class library: DUGen - the server now handles audio-rate inputs correctly
[supercollider.git] / SCClassLibrary / Common / Control / ResponseDefs.sc
blobfd0db8c9f1385aea288809e8dbd1bb33b5a3260e
1 AbstractResponderFunc {
2         classvar allFuncProxies;
3         var <func, <srcID, <enabled = false, <dispatcher, <permanent = false;
5         *initClass { Class.initClassTree(AbstractDispatcher); allFuncProxies = IdentitySet.new; }
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         }
16         disable {
17                 if(permanent.not, { CmdPeriod.remove(this) });
18                 dispatcher.remove(this);
19                 enabled = false;
20         }
22         prFunc_ {|newFunc|
23                 func = newFunc;
24                 this.changed(\function);
25         }
27         add {|newFunc| func = func.addFunc(newFunc); this.changed(\function); }
29         remove {|removeFunc| func = func.removeFunc(removeFunc); this.changed(\function); }
31         gui { this.subclassResponsibility(thisMethod) }
33         cmdPeriod { this.free }
35         oneShot {
36                 var oneShotFunc, wrappedFunc;
37                 wrappedFunc = func;
38                 oneShotFunc = { arg ...args; this.free; wrappedFunc.value(*args); };
39                 this.prFunc_(oneShotFunc);
40         }
42         permanent_{|bool|
43                 permanent = bool;
44                 if(bool && enabled, { CmdPeriod.remove(this) }, {CmdPeriod.add(this) })
45         }
47         fix { this.permanent_(true) }
49         free { allFuncProxies.remove(this); this.disable }
51         clear { this.prFunc_(nil) }
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         }
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         }
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         }
88 // defines the required interface
89 AbstractDispatcher {
90         classvar <all;
91         var registered = false;
93         *new { ^super.new.init; }
95         init { all.add(this);}
97         *initClass { all = IdentitySet.new }
99         add {|funcProxy| this.subclassResponsibility(thisMethod) } // proxies call this to add themselves to this dispatcher; should register this if needed
101         remove {|funcProxy| this.subclassResponsibility(thisMethod) } // proxies call this to remove themselves from this dispatcher; should unregister if needed
103         value { this.subclassResponsibility(thisMethod) }
105         valueArray {arg args; ^this.value(*args) } // needed to work in FunctionLists
107         register { this.subclassResponsibility(thisMethod) } // register this dispatcher to listen for its message type
109         unregister { this.subclassResponsibility(thisMethod) } // unregister this dispatcher so it no longer listens
111         free { this.unregister; all.remove(this) } // I'm done
113         typeKey { this.subclassResponsibility(thisMethod) } // a Symbol
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;
124         init { super.init; active = IdentityDictionary.new; wrappedFuncs = IdentityDictionary.new; }
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         }
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         }
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         }
156         wrapFunc { this.subclassResponsibility(thisMethod) }
158         getKeysForFuncProxy { |funcProxy| this.subclassResponsibility(thisMethod) }
160         update {|funcProxy, what| if(what == \function, { this.updateFuncForFuncProxy(funcProxy) }) }
162         free { wrappedFuncs.keys.do({|funcProxy| funcProxy.removeDependant(this) }); super.free }
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;
171         value { this.subclassResponsibility(thisMethod) }
173         valueArray {arg args; ^this.value(*args) } // needed to work in FunctionLists
177 ///////////////////// OSC
179 OSCMessageDispatcher : AbstractWrappingDispatcher {
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         }
196         getKeysForFuncProxy {|funcProxy| ^[funcProxy.path];}
198         value {|msg, time, addr, recvPort| active[msg[0]].value(msg, time, addr, recvPort);}
200         register {
201                 thisProcess.addOSCRecvFunc(this);
202                 registered = true;
203         }
205         unregister {
206                 thisProcess.removeOSCRecvFunc(this);
207                 registered = false;
208         }
210         typeKey { ^('OSC unmatched').asSymbol }
214 OSCMessagePatternDispatcher : OSCMessageDispatcher {
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         }
224         typeKey { ^('OSC matched').asSymbol }
228 OSCFunc : AbstractResponderFunc {
229         classvar <>defaultDispatcher, <>defaultMatchingDispatcher, traceFunc, traceRunning = false;
230         var <path, <recvPort, <argTemplate;
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         }
240         *new { arg func, path, srcID, recvPort, argTemplate, dispatcher;
241                 ^super.new.init(func, path, srcID, recvPort, argTemplate, dispatcher ? defaultDispatcher);
242         }
244         *newMatching { arg func, path, srcID, argTemplate, recvPort;
245                 ^super.new.init(func, path, srcID, recvPort, argTemplate, defaultMatchingDispatcher);
246         }
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         }
262         *cmdPeriod { this.trace(false) }
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                 if(recvPort.notNil and: {thisProcess.openUDPPort(recvPort).not}, {
271                         Error("Could not open UDP port"+recvPort).throw;
272                 });
273                 argtemplate = argtemplate.collect({|oscArg|
274                         if(oscArg.isKindOf(String), {oscArg.asSymbol}, {oscArg}); // match Symbols not Strings
275                 });
276                 argTemplate = argtemplate ? argTemplate;
277                 func = argfunc ? func;
278                 dispatcher = argdisp ? dispatcher;
279                 this.enable;
280                 allFuncProxies.add(this);
281         }
283         printOn { arg stream; stream << this.class.name << "(" <<* [path, srcID, argTemplate] << ")" }
287 OSCdef : OSCFunc {
288         classvar <all;
289         var <key;
291         *initClass {
292                 all = IdentityDictionary.new;
293         }
295         *new { arg key, func, path, srcID, recvPort, argTemplate, dispatcher;
296                 var res = all.at(key), wasDisabled;
297                 if(res.isNil) {
298                         ^super.new(func, path, srcID, recvPort, argTemplate, dispatcher).addToAll(key);
299                 } {
300                         if(func.notNil) {
301                                 wasDisabled = res.enabled.not;
302                                 res.disable;
303                                 try {
304                                         res.init(func, path, srcID, recvPort, argTemplate, dispatcher);
305                                         if(wasDisabled, { res.disable; });
306                                 } {|err|
307                                         res.free;
308                                         err.throw;
309                                 }
310                         }
311                 }
312                 ^res
313         }
315         *newMatching { arg key, func, path, srcID, argTemplate, recvPort;
316                 ^this.new(key, func, path, srcID, recvPort, argTemplate, defaultMatchingDispatcher);
317         }
319         addToAll {|argkey| key = argkey; all.put(key, this) }
321         free { all[key] = nil; super.free; }
323         printOn { arg stream; stream << this.class.name << "(" <<* [key, path, srcID, argTemplate] << ")" }
325         *freeAll {
326                 var objs = all.shallowCopy;
327                 objs.do(_.free)
328         }
332 // if you need to test for address func gets wrapped in this
333 OSCFuncAddrMessageMatcher : AbstractMessageMatcher {
334         var addr;
336         *new {|addr, func| ^super.new.init(addr, func);}
338         init {|argaddr, argfunc| addr = argaddr; func = argfunc; }
340         value {|msg, time, testAddr, recvPort|
341                 if(testAddr.addr == addr.addr and: {addr.port.matchItem(testAddr.port)}, {
342                         func.value(msg, time, testAddr, recvPort)
343                 })
344         }
347 // if you need to test for recvPort func gets wrapped in this
348 OSCFuncRecvPortMessageMatcher : AbstractMessageMatcher {
349         var recvPort;
351         *new {|recvPort, func| ^super.new.init(recvPort, func);}
353         init {|argrecvPort, argfunc| recvPort = argrecvPort; func = argfunc; }
355         value {|msg, time, addr, testRecvPort|
356                 if(testRecvPort == recvPort, {
357                         func.value(msg, time, addr, testRecvPort)
358                 })
359         }
362 OSCFuncBothMessageMatcher : AbstractMessageMatcher {
363         var addr, recvPort;
365         *new {|addr, recvPort, func| ^super.new.init(addr, recvPort, func);}
367         init {|argaddr, argrecvPort, argfunc| addr = argaddr; recvPort = argrecvPort; func = argfunc; }
369         value {|msg, time, testAddr, testRecvPort|
370                 if(testAddr.addr == addr.addr and: {addr.port.matchItem(testAddr.port)} and: {testRecvPort == recvPort}, {
371                         func.value(msg, time, testAddr, testRecvPort)
372                 })
373         }
376 OSCArgsMatcher : AbstractMessageMatcher {
377         var argTemplate;
379         *new{|argTemplate, func| ^super.new.init(argTemplate, func) }
381         init {|argArgTemplate, argFunc| argTemplate = argArgTemplate.asArray; func = argFunc; }
383         value {|testMsg, time, addr, recvPort|
384                 testMsg[1..].do({|item, i|
385                         if(argTemplate[i].matchItem(item).not, {^this});
386                 });
387                 func.value(testMsg, time, addr, recvPort)
388         }
391 ///////////////////// MIDI
393 // for \noteOn, \noteOff, \control, \polytouch
394 MIDIMessageDispatcher : AbstractWrappingDispatcher {
395         var >messageType;
397         *new {|messageType| ^super.new.messageType_(messageType) }
399         getKeysForFuncProxy {|funcProxy| ^(funcProxy.msgNum ? (0..127)).asArray;} // noteNum, etc.
401         value {|src, chan, num, val| active[num].value(val, num, chan, src);}
403         register {
404                 MIDIIn.perform(messageType.asSetter, MIDIIn.perform(messageType.asGetter).addFunc(this));
405                 registered = true;
406         }
408         unregister {
409                 MIDIIn.perform(messageType.asSetter, MIDIIn.perform(messageType.asGetter).removeFunc(this));
410                 registered = false;
411         }
413         // wrapper objects based on arg type and testing requirements
414         wrapFunc {|funcProxy|
415                 var func, chan, srcID, argTemplate;
416                 func = funcProxy.func;
417                 chan = funcProxy.chan;
418                 srcID = funcProxy.srcID;
419                 argTemplate = funcProxy.argTemplate;
420                 if(argTemplate.notNil, { func = MIDIValueMatcher(argTemplate, func)});
421                 ^case(
422                         { srcID.notNil && chan.isArray }, {MIDIFuncBothCAMessageMatcher(chan, srcID, func)},
423                         { srcID.notNil && chan.notNil }, {MIDIFuncBothMessageMatcher(chan, srcID, func)},
424                         { srcID.notNil }, {MIDIFuncSrcMessageMatcher(srcID, func)},
425                         { chan.isArray }, {MIDIFuncChanArrayMessageMatcher(chan, func)},
426                         { chan.notNil }, {MIDIFuncChanMessageMatcher(chan, func)},
427                         { func }
428                 );
429         }
431         typeKey { ^('MIDI ' ++ messageType).asSymbol }
434 // for \touch, \program, \bend
435 MIDIMessageDispatcherNV : MIDIMessageDispatcher {
437         getKeysForFuncProxy {|funcProxy| ^(funcProxy.chan ? (0..15)).asArray;} // chan
439         value {|src, chan, val| active[chan].value(val, chan, src);}
441         // wrapper objects based on arg type and testing requirements
442         wrapFunc {|funcProxy|
443                 var func, chan, srcID, argTemplate;
444                 func = funcProxy.func;
445                 chan = funcProxy.chan;
446                 srcID = funcProxy.srcID;
447                 argTemplate = funcProxy.argTemplate;
448                 if(argTemplate.notNil, { func = MIDIValueMatcher(argTemplate, func)});
449                 ^case(
450                         { srcID.notNil }, {MIDIFuncSrcMessageMatcherNV(srcID, func)},
451                         { func }
452                 );
453         }
457 MIDIFunc : AbstractResponderFunc {
458         classvar <>defaultDispatchers;
459         var <chan, <msgNum, <msgType, <argTemplate;
461         *initClass {
462                 defaultDispatchers = IdentityDictionary.new;
463                 [\noteOn, \noteOff, \control, \polytouch].do({|type|
464                         defaultDispatchers[type] = MIDIMessageDispatcher(type);
465                 });
466                 [\touch, \program, \bend].do({|type|
467                         defaultDispatchers[type] = MIDIMessageDispatcherNV(type);
468                 });
469         }
471         *new { arg func, msgNum, chan, msgType, srcID, argTemplate, dispatcher;
472                 ^super.new.init(func, msgNum, chan, msgType, srcID, argTemplate, dispatcher ? defaultDispatchers[msgType]);
473         }
475         *cc { arg func, ccNum, chan, srcID, argTemplate, dispatcher;
476                 ^this.new(func, ccNum, chan, \control, srcID, argTemplate, dispatcher);
477         }
479         *noteOn { arg func, noteNum, chan, srcID, argTemplate, dispatcher;
480                 ^this.new(func, noteNum, chan, \noteOn, srcID, argTemplate, dispatcher);
481         }
483         *noteOff { arg func, noteNum, chan, srcID, argTemplate, dispatcher;
484                 ^this.new(func, noteNum, chan, \noteOff, srcID, argTemplate, dispatcher);
485         }
487         *polytouch { arg func, noteNum, chan, srcID, argTemplate, dispatcher;
488                 ^this.new(func, noteNum, chan, \polytouch, srcID, argTemplate, dispatcher);
489         }
491         *touch { arg func, chan, srcID, argTemplate, dispatcher;
492                 ^this.new(func, nil, chan, \touch, srcID, argTemplate, dispatcher);
493         }
495         *bend { arg func, chan, srcID, argTemplate, dispatcher;
496                 ^this.new(func, nil, chan, \bend, srcID, argTemplate, dispatcher);
497         }
499         *program { arg func, chan, srcID, argTemplate, dispatcher;
500                 ^this.new(func, nil, chan, \program, srcID, argTemplate, dispatcher);
501         }
503         init {|argfunc, argmsgNum, argchan, argType, argsrcID, argtempl, argdisp|
504                 msgNum = argmsgNum ? msgNum;
505                 chan = argchan ? chan;
506                 srcID = argsrcID ? srcID;
507                 func = argfunc ? func;
508                 msgType = argType ? msgType;
509                 dispatcher = argdisp ? dispatcher;
510                 argTemplate = argtempl ? argTemplate;
511                 this.enable;
512                 allFuncProxies.add(this);
513         }
515         // post pretty
516         printOn { arg stream; stream << this.class.name << "(" <<* [msgType, msgNum, chan, argTemplate] << ")" }
520 MIDIdef : MIDIFunc {
521         classvar <>all; // same as other def classes, do we need a setter really?
522         var <key;
524         *initClass {
525                 all = IdentityDictionary.new;
526         }
528         *new { arg key, func, msgNum, chan, msgType, srcID, argTemplate, dispatcher;
529                 var res = all.at(key);
530                 if(res.isNil) {
531                         ^super.new(func, msgNum, chan, msgType, srcID, argTemplate, dispatcher).addToAll(key);
532                 } {
533                         if(func.notNil) {
534                                 if(res.enabled, {
535                                         res.disable;
536                                         res.init(func, msgNum, chan, msgType, srcID, argTemplate, dispatcher ? defaultDispatchers[msgType]);
537                                 }, { res.init(func, msgNum, chan, msgType, srcID, argTemplate, dispatcher ? defaultDispatchers[msgType]).disable; });
538                         }
539                 }
540                 ^res
541         }
543         *cc { arg key, func, ccNum, chan, srcID, argTemplate, dispatcher;
544                 ^this.new(key, func, ccNum, chan, \control, srcID, argTemplate, dispatcher);
545         }
547         *noteOn { arg key, func, noteNum, chan, srcID, argTemplate, dispatcher;
548                 ^this.new(key, func, noteNum, chan, \noteOn, srcID, argTemplate, dispatcher);
549         }
551         *noteOff { arg key, func, noteNum, chan, srcID, argTemplate, dispatcher;
552                 ^this.new(key, func, noteNum, chan, \noteOff, srcID, argTemplate, dispatcher);
553         }
555         *polytouch { arg key, func, noteNum, chan, srcID, argTemplate, dispatcher;
556                 ^this.new(key, func, noteNum, chan, \polytouch, srcID, argTemplate, dispatcher);
557         }
559         *touch { arg key, func, chan, srcID, argTemplate, dispatcher;
560                 ^this.new(key, func, nil, chan, \touch, srcID, argTemplate, dispatcher);
561         }
563         *bend { arg key, func, chan, srcID, argTemplate, dispatcher;
564                 ^this.new(key, func, nil, chan, \bend, srcID, argTemplate, dispatcher);
565         }
567         *program { arg key, func, chan, srcID, argTemplate, dispatcher;
568                 ^this.new(key, func, nil, chan, \program, srcID, argTemplate, dispatcher);
569         }
571         addToAll {|argkey| key = argkey; all.put(key, this) }
573         free { all[key] = nil; super.free; }
575         // post pretty
576         printOn { arg stream; stream << this.class.name << "(" <<* [key, msgType, msgNum, chan, argTemplate] << ")" }
578         *freeAll {
579                 var objs = all.shallowCopy;
580                 objs.do(_.free)
581         }
585 // if you need to test for srcID func gets wrapped in this
586 MIDIFuncSrcMessageMatcher : AbstractMessageMatcher {
587         var srcID;
589         *new {|srcID, func| ^super.new.init(srcID, func);}
591         init {|argsrcID, argfunc| srcID = argsrcID; func = argfunc; }
593         value {|value, num, chan, testSrc|
594                 if(srcID == testSrc, {func.value(value, num, chan, testSrc)})
595         }
598 // if you need to test for srcID func gets wrapped in this
599 MIDIFuncChanMessageMatcher : AbstractMessageMatcher {
600         var chan;
602         *new {|chan, func| ^super.new.init(chan, func);}
604         init {|argchan, argfunc| chan = argchan; func = argfunc; }
606         value {|value, num, testChan, srcID|
607                 if(chan == testChan, {func.value(value, num, testChan, srcID)})
608         }
611 // if you need to test for chanArray func gets wrapped in this
612 MIDIFuncChanArrayMessageMatcher : AbstractMessageMatcher {
613         var chanBools;
615         *new {|chanArray, func|
616                 var chanBools;
617                 // lookup bool by index fastest, so construct an Array here
618                 chanBools = Array.fill(16, {|i| chanArray.includes(i) });
619                 ^super.new.init(chanBools, func);
620         }
622         init {|argchanbools, argfunc| chanBools = argchanbools; func = argfunc; }
624         value {|value, num, testChan, srcID|
625                 // lookup bool by index fastest
626                 if(chanBools[testChan], {func.value(value, num, testChan, srcID)})
627         }
630 // version for message types which don't pass a val
631 MIDIFuncSrcMessageMatcherNV : MIDIFuncSrcMessageMatcher {
633         value {|num, chan, testSrc|
634                 if(srcID == testSrc, {func.value(num, chan, testSrc)})
635         }
638 // if you need to test for chan and srcID func gets wrapped in this
639 MIDIFuncBothMessageMatcher : AbstractMessageMatcher {
640         var chan, srcID;
642         *new {|chan, srcID, func| ^super.new.init(chan, srcID, func);}
644         init {|argchan, argsrcID, argfunc| chan = argchan; srcID = argsrcID; func = argfunc; }
646         value {|value, num, testChan, testSrc|
647                 if(srcID == testSrc and: {chan == testChan}, {func.value(value, num, testChan, testSrc)})
648         }
652 // if you need to test for chanArray and srcID func gets wrapped in this
653 MIDIFuncBothCAMessageMatcher : AbstractMessageMatcher {
654         var chanBools, srcID;
656         *new {|chanArray, srcID, func|
657                 var chanBools;
658                 // lookup bool by index fastest, so construct an Array here
659                 chanBools = Array.fill(16, {|i| chanArray.includes(i) });
660                 ^super.new.init(chanBools, srcID, func);
661         }
663         init {|argbools, argsrcID, argfunc| chanBools = argbools; srcID = argsrcID; func = argfunc; }
665         value {|value, num, testChan, testSrc|
666                 if(srcID == testSrc and: {chanBools[testChan]}, {func.value(value, num, testChan, testSrc)})
667         }
670 // if you want to test for the actual message value, the func gets wrapped in this
671 MIDIValueMatcher : AbstractMessageMatcher {
672         var argTemplate;
674         *new{|argTemplate, func| ^super.new.init(argTemplate, func) }
676         init {|argArgTemplate, argFunc| argTemplate = argArgTemplate; func = argFunc; }
678         value {|...testMsg|
679                 if(argTemplate.matchItem(testMsg.first), {func.value(*testMsg)});
680         }