1// see MIDIResponder help for all classes on this page
2
3
4MIDIResponder {
5	var	<>function,<>swallowEvent=false,
6		<>matchEvent;		// for matching ports, channels, and parameters
7	init { arg install;
8		if(this.class.initialized.not,{ this.class.init });
9		matchEvent.port = matchEvent.port.asMIDIInPortUID;
10		if(install,{this.class.add(this);});
11	}
12	respond { arg src,chan,num,value;
13		if(this.match(src,chan,num,value),{
14			this.value(src,chan,num,value)
15			^swallowEvent
16		});
17		^false;
18	}
19	match { arg src,chan,num,value;
20		^matchEvent.match(src,chan,num,value);
21	}
22	value { arg src,chan,a,b;
23		function.value(src, chan, a, b)
24	}
25
26	remove {
27		this.class.remove(this)
28	}
29	*removeAll {
30		if(this == MIDIResponder,{
31			this.allSubclasses.do({ |responderClass| responderClass.removeAll })
32		},{
33			this.init
34		})
35	}
36}
37
38
39
40NoteOnResponder : MIDIResponder {
41	classvar <norinit = false,<nonr;
42
43	*new { arg function, src, chan, num, veloc, install=true,swallowEvent=false;
44		num = num.isNumber.if({ num.asInteger }, num);
45		num = num.isCollection.if({ num.collect(_.asInteger) }, num);
46		^super.new.function_(function)
47			.matchEvent_(MIDIEvent(nil, src, chan, num, veloc))
48			.swallowEvent_(swallowEvent)
49			.init(install)
50	}
51	*initialized { ^norinit }
52	*responders { ^nonr }
53	*init {
54		if(MIDIClient.initialized.not,{ MIDIIn.connectAll });
55		if(norinit.not) {
56			MIDIIn.addFuncTo(\noteOn, { arg src, chan, note, veloc;
57				nonr.any({ arg r;
58					r.respond(src,chan,note,veloc)
59				});
60			})
61		};
62		norinit = true;
63		nonr = [];
64	}
65	*add { arg resp;
66		nonr = nonr.add(resp);
67	}
68	*remove { arg resp;
69		nonr.remove(resp);
70	}
71	learn {
72		var oneShot;
73		oneShot = this.class.new({ |src,chan,num,value|
74					this.matchEvent_(MIDIEvent(nil,src,chan,nil,nil));
75					oneShot.remove;
76				},nil,nil,nil,nil,true,true)
77	}
78}
79
80NoteOffResponder : NoteOnResponder {
81	classvar <noffinit = false,<noffr;
82
83	*init {
84		if(MIDIClient.initialized.not,{ MIDIIn.connectAll });
85		if(noffinit.not) {
86			MIDIIn.addFuncTo(\noteOff, { arg src, chan, note, veloc;
87				noffr.any({ arg r;
88					r.respond(src,chan,note,veloc)
89				});
90			})
91		};
92		noffinit = true;
93		noffr = [];
94	}
95	*initialized { ^noffinit }
96	*responders { ^noffr }
97
98	*add { arg resp;
99		noffr = noffr.add(resp);
100	}
101	*remove { arg resp;
102		noffr.remove(resp);
103	}
104}
105
106CCResponder : MIDIResponder {
107	classvar <ccinit = false,<ccr,<ccnumr;
108
109	*new { arg function, src, chan, num, value, install=true,swallowEvent=false;
110		num = num.isNumber.if({ num.asInteger }, num);
111		num = num.isCollection.if({ num.collect(_.asInteger) }, num);
112		^super.new.function_(function).swallowEvent_(swallowEvent)
113			.matchEvent_(MIDIEvent(nil, src, chan, num, value))
114			.init(install)
115	}
116	*initialized { ^ccinit }
117	*responders { ^ccnumr.select(_.notNil).flat ++ ccr }
118	*add { arg resp;
119		var temp;
120		if(this.initialized.not,{ this.init });
121		if((temp = resp.matchEvent.ctlnum).isNumber) {
122			ccnumr[temp] = ccnumr[temp].add(resp);
123		} {
124			ccr = ccr.add(resp);
125		};
126	}
127	*remove { arg resp;
128		var temp;
129		if((temp = resp.matchEvent.ctlnum).isNumber) {
130			ccnumr[temp].remove(resp)
131		} {
132			ccr.remove(resp);
133		};
134	}
135	*init {
136		if(MIDIClient.initialized.not,{ MIDIIn.connectAll });
137		if(ccinit.not) {
138			MIDIIn.addFuncTo(\control, { arg src,chan,num,val;
139				// first try cc num specific
140				// then try non-specific (matches any cc )
141				[ccnumr[num], ccr].any({ |stack|
142					stack.notNil and: {stack.any({ |r| r.respond(src,chan,num,val) })}
143				})
144			});
145		};
146		ccinit = true;
147		ccr = [];
148		ccnumr = Array.newClear(128);
149	}
150	learn {
151		var oneShot;
152		oneShot = CCResponder({ |src,chan,num,value|
153					this.matchEvent_(MIDIEvent(nil,src,chan,num,nil));
154					oneShot.remove;
155				},nil,nil,nil,nil,true,true)
156	}
157
158	matchEvent_ { |midiEvent|
159			// if ctlnum changes from non-number to number, or vice versa,
160			// this responder is going to move between ccr and ccnumr
161		if(matchEvent.notNil and:
162				{ matchEvent.ctlnum.isNumber !== midiEvent.ctlnum.isNumber })
163		{
164			this.remove;
165			matchEvent = midiEvent;
166			this.class.add(this);
167		} {
168			matchEvent = midiEvent;
169		}
170	}
171}
172
173TouchResponder : MIDIResponder {
174	classvar <touchinit = false,<touchr;
175
176	*new { arg function, src, chan, value, install=true,swallowEvent=false;
177		^super.new.function_(function).swallowEvent_(swallowEvent)
178			.matchEvent_(MIDIEvent(nil, src, chan, nil, value))
179			.init(install)
180	}
181	*init {
182		if(MIDIClient.initialized.not,{ MIDIIn.connectAll });
183		if(touchinit.not) {
184			MIDIIn.addFuncTo(\touch, { arg src, chan, val;
185				touchr.any({ arg r;
186					r.respond(src,chan,nil,val)
187				})
188			})
189		};
190		touchinit = true;
191		touchr = [];
192	}
193	value { arg src,chan,num,val;
194		// num is irrelevant
195		function.value(src,chan,val);
196	}
197	*initialized { ^touchinit }
198	*responders { ^touchr }
199
200	*add { arg resp;
201		touchr = touchr.add(resp);
202	}
203	*remove { arg resp;
204		touchr.remove(resp);
205	}
206	learn {
207		var oneShot;
208		oneShot = this.class.new({ |src,chan,num,value|
209					this.matchEvent_(MIDIEvent(nil,src,chan,nil,nil));
210					oneShot.remove;
211				},nil,nil,nil,true,true)
212	}
213}
214
215BendResponder : TouchResponder {
216	classvar <bendinit = false,<bendr;
217
218	*init {
219		if(MIDIClient.initialized.not,{ MIDIIn.connectAll });
220		if(bendinit.not) {
221			MIDIIn.addFuncTo(\bend, { arg src, chan, val;
222				bendr.any({ arg r;
223					r.respond(src,chan,nil,val)
224				});
225			})
226		};
227		bendinit = true;
228		bendr = [];
229	}
230	*initialized { ^bendinit }
231	*responders { ^bendr }
232
233	*add { arg resp;
234		bendr = bendr.add(resp);
235	}
236	*remove { arg resp;
237		bendr.remove(resp);
238	}
239}
240
241/*
242NoteOnOffResponder
243	the note on function would return an object which is stored.
244	when a matching note off event occurs, the object is passed into the note off function
245PolyTouchResponder
246*/
247
248
249
250ProgramChangeResponder : MIDIResponder {
251	classvar <pcinit = false,<pcr;
252
253	*new { arg function, src, chan, value, install=true;
254		^super.new.function_(function)
255			.matchEvent_(MIDIEvent(nil, src.asMIDIInPortUID, chan, nil, value))
256			.init(install)
257	}
258	*init {
259		if(MIDIClient.initialized.not,{ MIDIIn.connectAll });
260		if(pcinit.not) {
261			MIDIIn.addFuncTo(\program, { arg src, chan, val;
262				pcr.do({ arg r;
263					if(r.matchEvent.match(src, chan, nil, val))
264					{ r.value(src,chan,val) };
265				});
266			})
267		};
268		pcinit = true;
269		pcr = [];
270	}
271	value { arg src,chan,val;
272		function.value(src,chan,val);
273	}
274	*initialized { ^pcinit }
275	*responders { ^pcr }
276
277	*add { arg resp;
278		pcr = pcr.add(resp);
279	}
280	*remove { arg resp;
281		pcr.remove(resp);
282	}
283}
284