HelpBrowser: path box becomes a more conventional search box
[supercollider.git] / SCClassLibrary / Common / Control / MIDIOut.sc
blob98bb9edf381b70a8a0ccae65b18324b36c6c4f1c
1 MIDIEndPoint {
2         var <>device, <>name, <>uid;
3         *new{ arg device, name, uid;
4                 ^super.newCopyArgs(device, name, uid)
5         }
6         printOn { arg stream;
7                 stream << this.class.name << "(" <<<*
8                         [device, name]  <<")"
9         }
12 MIDIClient {
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
18                 this.prInitClient;
19                 this.list;
20                 if(inports.isNil,{inports = sources.size});
21                 if(outports.isNil,{outports = destinations.size});
22 //                      this.disposeClient;
24                 this.prInit(inports,outports);
25                 initialized = true;
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
28                 // so we warn you
29                 if(sources.size < inports or: {destinations.size < outports},{
30                         "WARNING:".postln;
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;
36                 });
38                 this.list;
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 });
46         }
47         *list {
48                 var list;
49                 list = this.prList;
50                 if(list.notNil, {
51                         sources = list.at(0).collect({ arg id,i;
52                                 MIDIEndPoint(list.at(1).at(i), list.at(2).at(i), id)
53                         });
54                         destinations = list.at(3).collect({arg id, i;
55                                 MIDIEndPoint(list.at(5).at(i), list.at(4).at(i), id)
56                         });
57                 });
58         }
59         *prInit { arg inports, outports;
60                 _InitMIDI
61                 ^this.primitiveFailed
62         }
63         *prInitClient {
64                 _InitMIDIClient
65                 ^this.primitiveFailed
66         }
67         *prList {
68                 _ListMIDIEndpoints
69                 ^this.primitiveFailed
70         }
71         *disposeClient {
72                 _DisposeMIDIClient
73                 ^this.primitiveFailed
74         }
75         *restart {
76                 _RestartMIDI
77                 ^this.primitiveFailed
78         }
82 MIDIEvent {
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)
87         }
88         set { arg inStatus, inPort, inChan, inB, inC, inThread;
89                 status = inStatus;
90                 port = inPort;
91                 chan = inChan;
92                 b = inB;
93                 c = inC;
94                 inThread !? { thread = inThread };
95         }
96         match { arg inPort, inChan, inB, inC;
97                 ^port.matchItem(inPort) and: {
98                         chan.matchItem(inChan) and: {
99                         b.matchItem(inB) and: {
100                         c.matchItem(inC)
101                 }}}
102         }
103         // convenience accessors
104         note { ^b }
105         veloc { ^c }
106         ctlnum { ^b }
107         ctlval { ^c }
110 MIDIIn {
111         var <>port;
112         classvar <>action,
113         <> noteOn, <> noteOff, <> polytouch,
114         <> control, <> program,
115         <> touch, <> bend,
116         <> sysex, sysexPacket, <> sysrt, <> smpte, <> invalid;
118         classvar
119         <> noteOnList, <> noteOffList, <> polyList,
120         <> controlList, <> programList,
121         <> touchList, <> bendList;
122         
123         // safer than global setters
124         *addFuncTo { |what, func|
125                 this.perform(what.asSetter, this.perform(what).addFunc(func))
126         }
127         
128         *removeFuncTo { |what, func|
129                 this.perform(what.asSetter, this.perform(what).removeFunc(func))
130         }
131         
132         *replaceFuncTo { |what, func, newFunc|
133                 this.perform(what.asSetter, this.perform(what).replaceFunc(func, newFunc))
134         }
135         
136         *waitNoteOn { arg port, chan, note, veloc;
137                 var event;
138                 event = MIDIEvent(\noteOn, port, chan, note, veloc, thisThread);
139                 noteOnList = noteOnList.add(event); // add to waiting list
140                 nil.yield; // pause the thread.
141                 ^event
142         }
143         *waitNoteOff { arg port, chan, note, veloc;
144                 var event;
145                 event = MIDIEvent(\noteOff, port, chan, note, veloc, thisThread);
146                 noteOffList = noteOffList.add(event); // add to waiting list
147                 nil.yield; // pause the thread.
148                 ^event
149         }
150         *waitPoly { arg port, chan, note, veloc;
151                 var event;
152                 event = MIDIEvent(\poly, port, chan, note, veloc, thisThread);
153                 polyList = polyList.add(event); // add to waiting list
154                 nil.yield; // pause the thread.
155                 ^event
156         }
157         *waitTouch { arg port, chan, val;
158                 var event;
159                 event = MIDIEvent(\touch, port, chan, val, nil, thisThread);
160                 touchList = touchList.add(event); // add to waiting list
161                 nil.yield; // pause the thread.
162                 ^event
163         }
164         *waitControl { arg port, chan, num, val;
165                 var event;
166                 event = MIDIEvent(\control, port, chan, num, val, thisThread);
167                 controlList = controlList.add(event); // add to waiting list
168                 nil.yield; // pause the thread.
169                 ^event
170         }
171         *waitBend { arg port, chan, val;
172                 var event;
173                 event = MIDIEvent(\bend, port, chan, val, nil, thisThread);
174                 bendList = bendList.add(event); // add to waiting list
175                 nil.yield; // pause the thread.
176                 ^event
177         }
178         *waitProgram { arg port, chan, num;
179                 var event;
180                 event = MIDIEvent(\program, port, chan, num, nil, thisThread);
181                 programList = programList.add(event); // add to waiting list
182                 nil.yield; // pause the thread.
183                 ^event
184         }
186         *doAction { arg src, status, a, b, c;
187                 action.value(src, status, a, b, c);
188         }
189         *doNoteOnAction { arg src, chan, num, veloc;
190                 noteOn.value(src, chan, num, veloc);
191                 this.prDispatchEvent(noteOnList, \noteOn, src, chan, num, veloc);
192         }
193         *doNoteOffAction { arg src, chan, num, veloc;
194                 noteOff.value(src, chan, num, veloc);
195                 this.prDispatchEvent(noteOffList, \noteOff, src, chan, num, veloc);
196         }
197         *doPolyTouchAction { arg src, chan, num, val;
198                 polytouch.value(src, chan, num, val);
199                 this.prDispatchEvent(polyList, \poly, src, chan, num, val);
200         }
201         *doControlAction { arg src, chan, num, val;
202                 control.value(src, chan, num, val);
203                 this.prDispatchEvent(controlList, \control, src, chan, num, val);
204         }
205         *doProgramAction { arg src, chan, val;
206                 program.value(src, chan, val);
207                 this.prDispatchEvent(programList, \program, src, chan, val);
208         }
209         *doTouchAction { arg src, chan, val;
210                 touch.value(src, chan, val);
211                 this.prDispatchEvent(touchList, \touch, src, chan, val);
212         }
213         *doBendAction { arg src, chan, val;
214                 bend.value(src, chan, val);
215                 this.prDispatchEvent(bendList, \bend, src, chan, val);
216         }
218         *doSysexAction { arg src,  packet;
219                 sysexPacket = sysexPacket ++ packet;
220                 if (packet.last == -9, {
221                         sysex.value(src, sysexPacket);
222                         sysexPacket = nil
223                 });
224         }
225         *doInvalidSysexAction { arg src, packet;
226                 invalid.value(src, packet);
227         }
229         *doSysrtAction { arg src, index, val;
230                 sysrt.value(src, index, val);
231         }
233         *doSMPTEaction { arg src, frameRate, timecode;
234                 smpte.value(src, frameRate, timecode);
235         }
237         *findPort { arg deviceName,portName;
238                 ^MIDIClient.sources.detect({ |endPoint| endPoint.device == deviceName and: {endPoint.name == portName}});
239         }
240         *connectAll {
241                 if(MIDIClient.initialized.not,{ MIDIClient.init });
242                 MIDIClient.sources.do({ |src,i|
243                         MIDIIn.connect(i,src);
244                 });
245         }
246         *connect { arg inport=0, device=0;
247                 var uid,source;
248                 if(MIDIClient.initialized.not,{ MIDIClient.init });
249                 if(device.isNumber, {
250                         if(device >= 0, {
251                                 if ( device > MIDIClient.sources.size,{ // on linux the uid's are very large numbers
252                                         source = MIDIClient.sources.detect{ |it| it.uid == device };
253                                         if(source.isNil,{
254                                                 ("MIDI device with uid"+device+ "not found").warn;
255                                         },{
256                                                 uid = source.uid;
257                                         })
258                                 },{
259                                         source = MIDIClient.sources.at(device);
260                                         if(source.isNil,{
261                                                 "MIDIClient failed to init".warn;
262                                         },{
263                                                 uid = MIDIClient.sources.at(device).uid;
264                                         });
265                                 });
266                         },{ // elsewhere they tend to be negative
267                                 uid = device;
268                         });
269                 },{
270                         if(device.isKindOf(MIDIEndPoint), {uid = device.uid}); // else error
271                 });
272                 this.connectByUID(inport,uid);
273         }
274         *disconnect { arg inport=0, device=0;
275                 var uid, source;
276                 if(device.isKindOf(MIDIEndPoint), {uid = device.uid});
277                 if(device.isNumber, {
278                         if(device.isPositive, {
279                                 if ( device > MIDIClient.sources.size,
280                                         {
281                                                 source = MIDIClient.sources.select{ |it| it.uid == device }.first;
282                                                 if(source.isNil,{
283                                                         ("MIDI device with uid"+device+ "not found").warn;
284                                                 },{
285                                                         uid = source.uid;
286                                                 })
287                                         },
288                                         {
289                                                 source = MIDIClient.sources.at(device);
290                                                 if(source.isNil,{
291                                                         "MIDIClient failed to init".warn;
292                                                 },{
293                                                         uid = MIDIClient.sources.at(device).uid;
294                                                 });
295                                         });
296                         },{
297                                 uid = device;
298                         });
299                 });
300                 this.disconnectByUID(inport,uid);
301         }
302         *connectByUID {arg inport, uid;
303                 _ConnectMIDIIn
304         }
305         *disconnectByUID {arg inport, uid;
306                 _DisconnectMIDIIn
307         }
309         *new { arg port;
310                 ^super.new.port_(port)
311         }
313         *prDispatchEvent { arg eventList, status, port, chan, b, c;
314                 var selectedEvents;
315                 eventList ?? {^this};
316                 eventList.takeThese {| event |
317                         if (event.match(port, chan, b, c))
318                         {
319                                 selectedEvents = selectedEvents.add(event);
320                                 true
321                         }
322                         { false };
323                 };
324                 selectedEvents.do{ |event|
325                                 event.set(status, port, chan, b, c);
326                                 event.thread.next;
327                 }
328         }
331 MIDIOut {
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 });
337                 } {
338                         ^super.newCopyArgs(port, uid ?? 0 );
339                 }
340         }
341         *newByName { arg deviceName,portName,dieIfNotFound=true;
342                 var endPoint,index;
343                 endPoint = MIDIClient.destinations.detect({ |ep,epi|
344                         index = epi;
345                         ep.device == deviceName and: {ep.name == portName}
346                 });
347                 if(endPoint.isNil,{
348                         if(dieIfNotFound,{
349                                 Error("Failed to find MIDIOut port " + deviceName + portName).throw;
350                         },{
351                                 ("Failed to find MIDIOut port " + deviceName + portName).warn;
352                         });
353                 });
354                 ^this.new(index,endPoint.uid)
355         }
356         *findPort { arg deviceName,portName;
357                 ^MIDIClient.destinations.detect({ |endPoint| endPoint.device == deviceName and: {endPoint.name == portName}});
358         }
360         write { arg len, hiStatus, loStatus, a=0, b=0;
361                 this.send(port, uid, len, hiStatus, loStatus, a, b, latency);
362         }
364         noteOn { arg chan, note=60, veloc=64;
365                 this.write(3, 16r90, chan.asInteger, note.asInteger, veloc.asInteger);
366         }
367         noteOff { arg chan, note=60, veloc=64;
368                 this.write(3, 16r80, chan.asInteger, note.asInteger, veloc.asInteger);
369         }
370         polyTouch { arg chan, note=60, val=64;
371                 this.write(3, 16rA0, chan.asInteger, note.asInteger, val.asInteger);
372         }
373         control { arg chan, ctlNum=7, val=64;
374                 this.write(3, 16rB0, chan.asInteger, ctlNum.asInteger, val.asInteger);
375         }
376         program { arg chan, num=1;
377                 this.write(2, 16rC0, chan.asInteger, num.asInteger);
378         }
379         touch { arg chan, val=64;
380                 this.write(2, 16rD0, chan.asInteger, val.asInteger);
381         }
382         bend { arg chan, val=8192;
383                 val = val.asInteger;
384                 this.write(3, 16rE0, chan, val bitAnd: 127, val >> 7);
385         }
386         allNotesOff { arg chan;
387                 this.control(chan, 123, 0);
388         }
389         smpte   { arg frames=0, seconds=0, minutes=0, hours=0, frameRate = 3;
390                 var packet;
391                 packet = [frames, seconds, minutes, hours]
392                         .asInteger
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); });
397         }
398         songPtr { arg songPtr;
399                 songPtr = songPtr.asInteger;
400                 this.write(4, 16rF0, 16r02, songPtr & 16r7f, songPtr >> 7 & 16r7f);
401         }
402         songSelect { arg song;
403                 this.write(3, 16rF0, 16r03, song.asInteger);
404         }
405         midiClock {
406                 this.write(1, 16rF0, 16r08);
407         }
408         start {
409                 this.write(1, 16rF0, 16r0A);
410         }
411         continue {
412                 this.write(1, 16rF0, 16r0B);
413         }
414         stop {
415                 this.write(1, 16rF0, 16r0C);
416         }
417         reset {
418                 this.write(1, 16rF0, 16r0F);
419         }
421         sysex { arg packet;
422                 ^this.prSysex( uid, packet );
423         }
425         send { arg outport, uid, len, hiStatus, loStatus, a=0, b=0, late;
426                 _SendMIDIOut
427         }
429         prSysex { arg uid, packet;
430                 _SendSysex
431                 ^this.primitiveFailed;
432         }