2 var <>device, <>name, <>uid;
3 *new{ arg device, name, uid;
4 ^super.newCopyArgs(device, name, uid)
7 stream << this.class.name << "(" <<<*
13 classvar <sources, <destinations;
14 classvar <initialized=false;
15 *init { arg inports, outports; // by default initialize all available ports
16 // you still must connect to them using MIDIIn.connect
20 if(inports.isNil,{inports = sources.size});
21 if(outports.isNil,{outports = destinations.size});
22 // this.disposeClient;
24 this.prInit(inports,outports);
26 // might ask for 1 and get 2 if your device has it
27 // or you might ask for a 1 and get 0 of nothing is plugged in
29 if(sources.size < inports or: {destinations.size < outports},{
31 ("MIDIClient-init requested " ++ inports ++ " inport(s) and " ++ outports
32 ++ " outport(s),").postln;
33 ("but found only " ++ sources.size ++ " inport(s) and " ++ destinations.size
34 ++ " outport(s).").postln;
35 "Some expected MIDI devices may not be available.".postln;
40 UI.registerForShutdown( { this.disposeClient } );
42 Post << "MIDI Sources:" << Char.nl;
43 sources.do({ |x| Post << Char.tab << x << Char.nl });
44 Post << "MIDI Destinations:" << Char.nl;
45 destinations.do({ |x| Post << Char.tab << x << Char.nl });
51 sources = list.at(0).collect({ arg id,i;
52 MIDIEndPoint(list.at(1).at(i), list.at(2).at(i), id)
54 destinations = list.at(3).collect({arg id, i;
55 MIDIEndPoint(list.at(5).at(i), list.at(4).at(i), id)
59 *prInit { arg inports, outports;
83 var <>status, <>port, <>chan, <>b, <>c, <>thread;
85 *new { arg status, port, chan, b, c, thread;
86 ^super.newCopyArgs(status, port, chan, b, c, thread)
88 set { arg inStatus, inPort, inChan, inB, inC, inThread;
94 inThread !? { thread = inThread };
96 match { arg inPort, inChan, inB, inC;
97 ^port.matchItem(inPort) and: {
98 chan.matchItem(inChan) and: {
99 b.matchItem(inB) and: {
103 // convenience accessors
113 <> noteOn, <> noteOff, <> polytouch,
114 <> control, <> program,
116 <> sysex, sysexPacket, <> sysrt, <> smpte, <> invalid;
119 <> noteOnList, <> noteOffList, <> polyList,
120 <> controlList, <> programList,
121 <> touchList, <> bendList;
123 // safer than global setters
124 *addFuncTo { |what, func|
125 this.perform(what.asSetter, this.perform(what).addFunc(func))
128 *removeFuncTo { |what, func|
129 this.perform(what.asSetter, this.perform(what).removeFunc(func))
132 *replaceFuncTo { |what, func, newFunc|
133 this.perform(what.asSetter, this.perform(what).replaceFunc(func, newFunc))
136 *waitNoteOn { arg port, chan, note, veloc;
138 event = MIDIEvent(\noteOn, port, chan, note, veloc, thisThread);
139 noteOnList = noteOnList.add(event); // add to waiting list
140 nil.yield; // pause the thread.
143 *waitNoteOff { arg port, chan, note, veloc;
145 event = MIDIEvent(\noteOff, port, chan, note, veloc, thisThread);
146 noteOffList = noteOffList.add(event); // add to waiting list
147 nil.yield; // pause the thread.
150 *waitPoly { arg port, chan, note, veloc;
152 event = MIDIEvent(\poly, port, chan, note, veloc, thisThread);
153 polyList = polyList.add(event); // add to waiting list
154 nil.yield; // pause the thread.
157 *waitTouch { arg port, chan, val;
159 event = MIDIEvent(\touch, port, chan, val, nil, thisThread);
160 touchList = touchList.add(event); // add to waiting list
161 nil.yield; // pause the thread.
164 *waitControl { arg port, chan, num, val;
166 event = MIDIEvent(\control, port, chan, num, val, thisThread);
167 controlList = controlList.add(event); // add to waiting list
168 nil.yield; // pause the thread.
171 *waitBend { arg port, chan, val;
173 event = MIDIEvent(\bend, port, chan, val, nil, thisThread);
174 bendList = bendList.add(event); // add to waiting list
175 nil.yield; // pause the thread.
178 *waitProgram { arg port, chan, num;
180 event = MIDIEvent(\program, port, chan, num, nil, thisThread);
181 programList = programList.add(event); // add to waiting list
182 nil.yield; // pause the thread.
186 *doAction { arg src, status, a, b, c;
187 action.value(src, status, a, b, c);
189 *doNoteOnAction { arg src, chan, num, veloc;
190 noteOn.value(src, chan, num, veloc);
191 this.prDispatchEvent(noteOnList, \noteOn, src, chan, num, veloc);
193 *doNoteOffAction { arg src, chan, num, veloc;
194 noteOff.value(src, chan, num, veloc);
195 this.prDispatchEvent(noteOffList, \noteOff, src, chan, num, veloc);
197 *doPolyTouchAction { arg src, chan, num, val;
198 polytouch.value(src, chan, num, val);
199 this.prDispatchEvent(polyList, \poly, src, chan, num, val);
201 *doControlAction { arg src, chan, num, val;
202 control.value(src, chan, num, val);
203 this.prDispatchEvent(controlList, \control, src, chan, num, val);
205 *doProgramAction { arg src, chan, val;
206 program.value(src, chan, val);
207 this.prDispatchEvent(programList, \program, src, chan, val);
209 *doTouchAction { arg src, chan, val;
210 touch.value(src, chan, val);
211 this.prDispatchEvent(touchList, \touch, src, chan, val);
213 *doBendAction { arg src, chan, val;
214 bend.value(src, chan, val);
215 this.prDispatchEvent(bendList, \bend, src, chan, val);
218 *doSysexAction { arg src, packet;
219 sysexPacket = sysexPacket ++ packet;
220 if (packet.last == -9, {
221 sysex.value(src, sysexPacket);
225 *doInvalidSysexAction { arg src, packet;
226 invalid.value(src, packet);
229 *doSysrtAction { arg src, index, val;
230 sysrt.value(src, index, val);
233 *doSMPTEaction { arg src, frameRate, timecode;
234 smpte.value(src, frameRate, timecode);
237 *findPort { arg deviceName,portName;
238 ^MIDIClient.sources.detect({ |endPoint| endPoint.device == deviceName and: {endPoint.name == portName}});
241 if(MIDIClient.initialized.not,{ MIDIClient.init });
242 MIDIClient.sources.do({ |src,i|
243 MIDIIn.connect(i,src);
246 *connect { arg inport=0, device=0;
248 if(MIDIClient.initialized.not,{ MIDIClient.init });
249 if(device.isNumber, {
251 if ( device > MIDIClient.sources.size,{ // on linux the uid's are very large numbers
252 source = MIDIClient.sources.detect{ |it| it.uid == device };
254 ("MIDI device with uid"+device+ "not found").warn;
259 source = MIDIClient.sources.at(device);
261 "MIDIClient failed to init".warn;
263 uid = MIDIClient.sources.at(device).uid;
266 },{ // elsewhere they tend to be negative
270 if(device.isKindOf(MIDIEndPoint), {uid = device.uid}); // else error
272 this.connectByUID(inport,uid);
274 *disconnect { arg inport=0, device=0;
276 if(device.isKindOf(MIDIEndPoint), {uid = device.uid});
277 if(device.isNumber, {
278 if(device.isPositive, {
279 if ( device > MIDIClient.sources.size,
281 source = MIDIClient.sources.select{ |it| it.uid == device }.first;
283 ("MIDI device with uid"+device+ "not found").warn;
289 source = MIDIClient.sources.at(device);
291 "MIDIClient failed to init".warn;
293 uid = MIDIClient.sources.at(device).uid;
300 this.disconnectByUID(inport,uid);
302 *connectByUID {arg inport, uid;
305 *disconnectByUID {arg inport, uid;
310 ^super.new.port_(port)
313 *prDispatchEvent { arg eventList, status, port, chan, b, c;
315 eventList ?? {^this};
316 eventList.takeThese {| event |
317 if (event.match(port, chan, b, c))
319 selectedEvents = selectedEvents.add(event);
324 selectedEvents.do{ |event|
325 event.set(status, port, chan, b, c);
332 var <>port, <>uid, <>latency=0.2;
334 *new { arg port, uid;
335 if(thisProcess.platform.name != \linux) {
336 ^super.newCopyArgs(port, uid ?? { MIDIClient.destinations[port].uid });
338 ^super.newCopyArgs(port, uid ?? 0 );
341 *newByName { arg deviceName,portName,dieIfNotFound=true;
343 endPoint = MIDIClient.destinations.detect({ |ep,epi|
345 ep.device == deviceName and: {ep.name == portName}
349 Error("Failed to find MIDIOut port " + deviceName + portName).throw;
351 ("Failed to find MIDIOut port " + deviceName + portName).warn;
354 ^this.new(index,endPoint.uid)
356 *findPort { arg deviceName,portName;
357 ^MIDIClient.destinations.detect({ |endPoint| endPoint.device == deviceName and: {endPoint.name == portName}});
360 write { arg len, hiStatus, loStatus, a=0, b=0;
361 this.send(port, uid, len, hiStatus, loStatus, a, b, latency);
364 noteOn { arg chan, note=60, veloc=64;
365 this.write(3, 16r90, chan.asInteger, note.asInteger, veloc.asInteger);
367 noteOff { arg chan, note=60, veloc=64;
368 this.write(3, 16r80, chan.asInteger, note.asInteger, veloc.asInteger);
370 polyTouch { arg chan, note=60, val=64;
371 this.write(3, 16rA0, chan.asInteger, note.asInteger, val.asInteger);
373 control { arg chan, ctlNum=7, val=64;
374 this.write(3, 16rB0, chan.asInteger, ctlNum.asInteger, val.asInteger);
376 program { arg chan, num=1;
377 this.write(2, 16rC0, chan.asInteger, num.asInteger);
379 touch { arg chan, val=64;
380 this.write(2, 16rD0, chan.asInteger, val.asInteger);
382 bend { arg chan, val=8192;
384 this.write(3, 16rE0, chan, val bitAnd: 127, val >> 7);
386 allNotesOff { arg chan;
387 this.control(chan, 123, 0);
389 smpte { arg frames=0, seconds=0, minutes=0, hours=0, frameRate = 3;
391 packet = [frames, seconds, minutes, hours]
393 .collect({ arg v, i; [(i * 2 << 4) | (v & 16rF), (i * 2 + 1 << 4) | (v >> 4) ] });
394 packet = packet.flat;
395 packet.put(7, packet.at(7) | ( frameRate << 1 ) );
396 packet.do({ arg v; this.write(2, 16rF0, 16r01, v); });
398 songPtr { arg songPtr;
399 songPtr = songPtr.asInteger;
400 this.write(4, 16rF0, 16r02, songPtr & 16r7f, songPtr >> 7 & 16r7f);
402 songSelect { arg song;
403 this.write(3, 16rF0, 16r03, song.asInteger);
406 this.write(1, 16rF0, 16r08);
409 this.write(1, 16rF0, 16r0A);
412 this.write(1, 16rF0, 16r0B);
415 this.write(1, 16rF0, 16r0C);
418 this.write(1, 16rF0, 16r0F);
422 ^this.prSysex( uid, packet );
425 send { arg outport, uid, len, hiStatus, loStatus, a=0, b=0, late;
429 prSysex { arg uid, packet;
431 ^this.primitiveFailed;