1 /*
2     SuperCollider real time audio synthesis system
3     Copyright (c) 2002 James McCartney. All rights reserved.
4     http://www.audiosynth.com
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
19 */
20 
21 /*
22 changes by charles picasso 14/april/2008 (sysex parsing + added running status)
23 changes by jan trutzschler v. f. 9/9/2002
24 the midiReadProc calls doAction in the class MIDIIn.
25 with the arguments: inUid, status, chan, val1, val2
26 added prDisposeMIDIClient
27 added prRestartMIDI
28 19/9 call different actions,disconnect midiInPort, midiout: sendmidi
29 04/feb/03 prListMIDIEndpoints modification by Ron Kuivila added jt.
30 */
31 #if SC_IPHONE
32 #    include <mach/mach_time.h>
33 #else
34 #    include <CoreAudio/HostTime.h>
35 #    include <Carbon/Carbon.h>
36 #endif
37 #include <CoreMIDI/CoreMIDI.h>
38 #include <vector>
39 #include "SCBase.h"
40 #include "VMGlobals.h"
41 #include "PyrSymbolTable.h"
42 #include "PyrInterpreter.h"
43 #include "PyrKernel.h"
44 
45 #include "PyrPrimitive.h"
46 #include "PyrObjectProto.h"
47 #include "PyrPrimitiveProto.h"
48 #include "PyrKernelProto.h"
49 #include "SC_InlineUnaryOp.h"
50 #include "SC_InlineBinaryOp.h"
51 #include "PyrSched.h"
52 #include "GC.h"
53 
54 PyrSymbol* s_domidiaction;
55 PyrSymbol* s_midiNoteOnAction;
56 PyrSymbol* s_midiNoteOffAction;
57 PyrSymbol* s_midiTouchAction;
58 PyrSymbol* s_midiControlAction;
59 PyrSymbol* s_midiPolyTouchAction;
60 PyrSymbol* s_midiProgramAction;
61 PyrSymbol* s_midiBendAction;
62 PyrSymbol* s_midiSysexAction;
63 PyrSymbol* s_midiInvalidSysexAction;
64 PyrSymbol* s_midiSysrtAction;
65 PyrSymbol* s_midiSMPTEAction;
66 // jt
67 PyrSymbol* s_midiin;
68 PyrSymbol* s_numMIDIDev;
69 PyrSymbol* s_midiclient;
70 const int kMaxMidiPorts = 128;
71 MIDIClientRef gMIDIClient = 0;
72 MIDIPortRef gMIDIInPort[kMaxMidiPorts], gMIDIOutPort[kMaxMidiPorts];
73 int gNumMIDIInPorts = 0, gNumMIDIOutPorts = 0;
74 bool gMIDIInitialized = false;
75 // cp
76 static bool gSysexFlag = false;
77 static Byte gRunningStatus = 0;
78 std::vector<Byte> gSysexData;
79 
midiNotifyProc(const MIDINotification * msg,void * refCon)80 void midiNotifyProc(const MIDINotification* msg, void* refCon) {}
81 
82 extern bool compiledOK;
83 
84 #if 0
85 static void dumpSysexData() {
86 	if(gSysexData.size() <= 0)
87 		return;
88 	std::vector<Byte>::const_iterator iter = gSysexData.begin(), end = gSysexData.end();
89 	int i=0;
90 	while(iter != end) {
91 		if((i % 16) == 0 && (i > 0))
92 			printf("\n");
93 		++i;
94 		printf("%02X ", *iter++);
95 	}
96 	printf("\n");
97 	printf("sysex data dump size:  %i bytes.\n", gSysexData.size());
98 }
99 #endif
100 
sysexBegin()101 static void sysexBegin() {
102     gRunningStatus = 0; // clear running status
103     gSysexData.clear();
104     gSysexFlag = true;
105 }
106 
scCallSysexAction(PyrSymbol * action,int recoverFromUID)107 static void scCallSysexAction(PyrSymbol* action, int recoverFromUID) {
108     VMGlobals* g = gMainVMGlobals;
109     if (recoverFromUID) { // rebuild the VM so sc won't crash with two following calls
110         ++g->sp;
111         SetObject(g->sp, s_midiin->u.classobj); // Set the class MIDIIn
112         ++g->sp;
113         SetInt(g->sp, recoverFromUID); // src
114         ++g->sp;
115     }
116     PyrInt8Array* sysexArray = newPyrInt8Array(g->gc, gSysexData.size(), 0, true);
117     sysexArray->size = gSysexData.size();
118     std::copy(gSysexData.begin(), gSysexData.end(), sysexArray->b);
119     SetObject(g->sp, (PyrObject*)sysexArray); // chan argument unneeded as there
120     runInterpreter(g, action, 3); // special sysex action in the lang
121 }
122 
sysexEnd(int lastUID)123 static void sysexEnd(int lastUID) {
124     gSysexFlag = false;
125     scCallSysexAction(s_midiSysexAction, lastUID);
126 }
127 
sysexEndInvalid()128 static void sysexEndInvalid() {
129     gSysexFlag = false;
130     scCallSysexAction(s_midiInvalidSysexAction, 0);
131 }
132 
midiProcessSystemPacket(MIDIPacket * pkt,int chan)133 static int midiProcessSystemPacket(MIDIPacket* pkt, int chan) {
134     int index, data;
135     VMGlobals* g = gMainVMGlobals;
136     switch (chan) {
137     case 7: // added cp: Sysex EOX must be taken into account if first on data packet
138     case 0: {
139         int last_uid = 0;
140         int m = pkt->length;
141         Byte* p_pkt = pkt->data;
142         Byte pktval;
143 
144         while (m--) {
145             pktval = *p_pkt++;
146             if (pktval & 0x80) { // status byte
147                 if (pktval == 0xF7) { // end packet
148                     gSysexData.push_back(pktval); // add EOX
149                     if (gSysexFlag)
150                         sysexEnd(last_uid); // if last_uid != 0 rebuild the VM.
151                     else
152                         sysexEndInvalid(); // invalid 1 byte with only EOX can happen
153                     break;
154                 } else if (pktval == 0xF0) { // new packet
155                     if (gSysexFlag) { // invalid new one/should not happen -- but handle in case
156                         // store the last uid value previous to invalid data to rebuild VM after sysexEndInvalid call
157                         // since it may call sysexEnd() just after it !
158                         if (slotIntVal(g->sp - 1, &last_uid)) {
159                             post("error: failed retrieving uid value !");
160                             last_uid = -1;
161                         }
162                         sysexEndInvalid();
163                     }
164                     sysexBegin(); // new sysex in
165                     gSysexData.push_back(pktval); // add SOX
166                 } else { // abnormal data in middle of sysex packet
167                     gSysexData.push_back(pktval); // add it as an abort message
168                     sysexEndInvalid(); // flush invalid
169                     m = 0; // discard all packet
170                     break;
171                 }
172             } else if (gSysexFlag)
173                 gSysexData.push_back(pktval); // add Byte
174             else // garbage - handle in case - discard it
175                 break;
176         }
177         return (pkt->length - m);
178     } break;
179 
180     case 1:
181         index = pkt->data[1] >> 4;
182         data = pkt->data[1] & 0xf;
183         switch (index) {
184         case 1:
185         case 3:
186         case 5:
187         case 7: {
188             data = data << 4;
189         }
190         }
191         SetInt(g->sp, index); // chan unneeded
192         ++g->sp;
193         SetInt(g->sp, data); // special smpte action in the lang
194         runInterpreter(g, s_midiSMPTEAction, 4);
195         return 2;
196 
197     case 2: // songptr
198         ++g->sp;
199         SetInt(g->sp, (pkt->data[2] << 7) | pkt->data[1]); // val1
200         runInterpreter(g, s_midiSysrtAction, 4);
201         return 3;
202 
203     case 3: // song select
204         ++g->sp;
205         SetInt(g->sp, pkt->data[1]); // val1
206         runInterpreter(g, s_midiSysrtAction, 4);
207         return 2;
208 
209     case 6: // tunerequest
210     case 8: // clock
211     case 9: // tick
212     case 10: // start
213     case 11: // continue
214     case 12: // stop
215     case 14: // activeSense
216     case 15: // reset
217         gRunningStatus = 0; // clear running status
218         runInterpreter(g, s_midiSysrtAction, 3);
219         return 1;
220 
221     default:
222         g->sp -= 3; // nevermind
223         break;
224     }
225 
226     return (1);
227 }
228 
midiProcessPacket(MIDIPacket * pkt,size_t uid)229 static void midiProcessPacket(MIDIPacket* pkt, size_t uid) {
230     // jt
231     if (pkt) {
232         gLangMutex.lock(); // dont know if this is really needed/seems to be more stable..
233                            // it is needed  -jamesmcc
234         if (compiledOK) {
235             VMGlobals* g = gMainVMGlobals;
236 
237             int i = 0; // cp : changed uint8 to int if packet->length >= 256 bug:(infinite loop)
238             while (i < pkt->length) {
239                 uint8 status = pkt->data[i] & 0xF0;
240                 uint8 chan = pkt->data[i] & 0x0F;
241                 g->canCallOS = false; // cannot call the OS
242 
243                 ++g->sp;
244                 SetObject(g->sp, s_midiin->u.classobj); // Set the class MIDIIn
245                 // set arguments:
246                 ++g->sp;
247                 SetInt(g->sp, uid); // src
248                 // ++g->sp;  SetInt(g->sp, status); //status
249                 ++g->sp;
250                 SetInt(g->sp, chan); // chan
251 
252                 if (status & 0x80) // set the running status for voice messages
253                     gRunningStatus = ((status >> 4) == 0xF) ? 0 : pkt->data[i]; // keep also additional info
254             L:
255                 switch (status) {
256                 case 0x80: // noteOff
257                     ++g->sp;
258                     SetInt(g->sp, pkt->data[i + 1]); // val1
259                     ++g->sp;
260                     SetInt(g->sp, pkt->data[i + 2]); // val2
261                     runInterpreter(g, s_midiNoteOffAction, 5);
262                     i += 3;
263                     break;
264                 case 0x90: // noteOn
265                     ++g->sp;
266                     SetInt(g->sp, pkt->data[i + 1]); // val1
267                     ++g->sp;
268                     SetInt(g->sp, pkt->data[i + 2]); // val2
269                     // 					runInterpreter(g, pkt->data[i+2] ? s_midiNoteOnAction : s_midiNoteOffAction, 5);
270                     runInterpreter(g, s_midiNoteOnAction, 5);
271                     i += 3;
272                     break;
273                 case 0xA0: // polytouch
274                     ++g->sp;
275                     SetInt(g->sp, pkt->data[i + 1]); // val1
276                     ++g->sp;
277                     SetInt(g->sp, pkt->data[i + 2]); // val2
278                     runInterpreter(g, s_midiPolyTouchAction, 5);
279                     i += 3;
280                     break;
281                 case 0xB0: // control
282                     ++g->sp;
283                     SetInt(g->sp, pkt->data[i + 1]); // val1
284                     ++g->sp;
285                     SetInt(g->sp, pkt->data[i + 2]); // val2
286                     runInterpreter(g, s_midiControlAction, 5);
287                     i += 3;
288                     break;
289                 case 0xC0: // program
290                     ++g->sp;
291                     SetInt(g->sp, pkt->data[i + 1]); // val1
292                     runInterpreter(g, s_midiProgramAction, 4);
293                     i += 2;
294                     break;
295                 case 0xD0: // touch
296                     ++g->sp;
297                     SetInt(g->sp, pkt->data[i + 1]); // val1
298                     runInterpreter(g, s_midiTouchAction, 4);
299                     i += 2;
300                     break;
301                 case 0xE0: // bend
302                     ++g->sp;
303                     SetInt(g->sp, (pkt->data[i + 2] << 7) | pkt->data[i + 1]); // val1
304                     runInterpreter(g, s_midiBendAction, 4);
305                     i += 3;
306                     break;
307                 case 0xF0:
308                     i += midiProcessSystemPacket(pkt, chan);
309                     break;
310                 default: // data byte => continuing sysex message
311                     if (gRunningStatus && !gSysexFlag) { // modified cp: handling running status. may be we should here
312                         status = gRunningStatus & 0xF0; // accept running status only inside a packet beginning
313                         chan = gRunningStatus & 0x0F; // with a valid status byte ?
314                         SetInt(g->sp, chan);
315                         --i;
316                         goto L; // parse again with running status set
317                     }
318                     chan = 0;
319                     i += midiProcessSystemPacket(pkt, chan);
320                     break;
321                 }
322             }
323             g->canCallOS = false;
324         }
325         gLangMutex.unlock();
326     }
327 }
328 
midiReadProc(const MIDIPacketList * pktlist,void * readProcRefCon,void * srcConnRefCon)329 static void midiReadProc(const MIDIPacketList* pktlist, void* readProcRefCon, void* srcConnRefCon) {
330     MIDIPacket* pkt = (MIDIPacket*)pktlist->packet;
331     size_t uid = (size_t)srcConnRefCon;
332     for (uint32 i = 0; i < pktlist->numPackets; ++i) {
333         midiProcessPacket(pkt, uid);
334         pkt = MIDIPacketNext(pkt);
335     }
336 }
337 
338 int midiCleanUp();
339 
initMIDI(int numIn,int numOut)340 int initMIDI(int numIn, int numOut) {
341     OSStatus err;
342     CFAllocatorRef alloc = CFAllocatorGetDefault();
343     int enc = kCFStringEncodingMacRoman;
344 
345     midiCleanUp();
346     if (numIn > kMaxMidiPorts) {
347         printf("MIDI: note that maximum midi in ports is limited to %d.\n", kMaxMidiPorts);
348     }
349     if (numOut > kMaxMidiPorts) {
350         printf("MIDI: note that maximum midi out ports is limited to %d.\n", kMaxMidiPorts);
351     }
352     numIn = sc_clip(numIn, 1, kMaxMidiPorts);
353     numOut = sc_clip(numOut, 1, kMaxMidiPorts);
354 
355     if (!gMIDIClient) {
356         CFStringRef clientName = CFStringCreateWithCString(alloc, "SuperCollider", enc);
357 
358         err = MIDIClientCreate(clientName, midiNotifyProc, nil, &gMIDIClient);
359         if (err) {
360             post("Could not create MIDI client. error: ");
361             return errFailed;
362         }
363         CFRelease(clientName);
364     }
365 
366     for (int i = 0; i < numIn; ++i) {
367         char str[32];
368         sprintf(str, "in%d\n", i);
369         CFStringRef inputPortName = CFStringCreateWithCString(alloc, str, enc);
370 
371         err = MIDIInputPortCreate(gMIDIClient, inputPortName, midiReadProc, &i, gMIDIInPort + i);
372         if (err) {
373             gNumMIDIInPorts = i;
374             post("Could not create MIDI port %s. error %d\n", str, err);
375             return errFailed;
376         }
377         CFRelease(inputPortName);
378     }
379 
380     /*int n = MIDIGetNumberOfSources();
381     printf("%d sources\n", n);
382     for (i = 0; i < n; ++i) {
383         MIDIEndpointRef src = MIDIGetSource(i);
384         MIDIPortConnectSource(inPort, src, NULL);
385     }*/
386 
387     gNumMIDIInPorts = numIn;
388 
389     for (int i = 0; i < numOut; ++i) {
390         char str[32];
391         sprintf(str, "out%d\n", i);
392         CFStringRef outputPortName = CFStringCreateWithCString(alloc, str, enc);
393 
394         err = MIDIOutputPortCreate(gMIDIClient, outputPortName, gMIDIOutPort + i);
395         if (err) {
396             gNumMIDIOutPorts = i;
397             post("Could not create MIDI out port. error %d\n", err);
398             return errFailed;
399         }
400 
401         CFRelease(outputPortName);
402     }
403     gNumMIDIOutPorts = numOut;
404     return errNone;
405 }
406 
midiCleanUp()407 int midiCleanUp() {
408     /*
409      * do not catch errors when disposing ports
410      * MIDIClientDispose should normally dispose the ports attached to it
411      * but clean up the pointers in case
412      */
413     int i = 0;
414     for (i = 0; i < gNumMIDIOutPorts; ++i) {
415         MIDIPortDispose(gMIDIOutPort[i]);
416         gMIDIOutPort[i] = 0;
417     }
418     gNumMIDIOutPorts = 0;
419 
420     for (i = 0; i < gNumMIDIInPorts; ++i) {
421         MIDIPortDispose(gMIDIInPort[i]);
422         gMIDIInPort[i] = 0;
423     }
424     gNumMIDIInPorts = 0;
425 
426     // Disposing and immediately re-initializing an identical midi client can cause crashes. Don't dispose - instead,
427     // reuse the one we've created already.
428     //	if (gMIDIClient) {
429     //		if( MIDIClientDispose(gMIDIClient) ) {
430     //			post( "Error: failed to dispose MIDIClient\n" );
431     //			return errFailed;
432     //		}
433     //		gMIDIClient = 0;
434     //	}
435     return errNone;
436 }
437 
438 
midiListEndpoints()439 void midiListEndpoints() {}
440 
441 
442 int prListMIDIEndpoints(struct VMGlobals* g, int numArgsPushed);
prListMIDIEndpoints(struct VMGlobals * g,int numArgsPushed)443 int prListMIDIEndpoints(struct VMGlobals* g, int numArgsPushed) {
444     OSStatus error;
445     PyrSlot* a = g->sp;
446     int numSrc = MIDIGetNumberOfSources();
447     int numDst = MIDIGetNumberOfDestinations();
448 
449     PyrObject* idarray = newPyrArray(g->gc, 6 * sizeof(PyrObject), 0, true);
450     SetObject(a, idarray); // this is okay here as we don't use the receiver
451 
452     PyrObject* idarraySo = newPyrArray(g->gc, numSrc * sizeof(SInt32), 0, true);
453     SetObject(idarray->slots + idarray->size++, idarraySo);
454     g->gc->GCWriteNew(idarray, idarraySo); // we know idarraySo is white so we can use GCWriteNew
455 
456     PyrObject* devarraySo = newPyrArray(g->gc, numSrc * sizeof(PyrObject), 0, true);
457     SetObject(idarray->slots + idarray->size++, devarraySo);
458     g->gc->GCWriteNew(idarray, devarraySo); // we know devarraySo is white so we can use GCWriteNew
459 
460     PyrObject* namearraySo = newPyrArray(g->gc, numSrc * sizeof(PyrObject), 0, true);
461     SetObject(idarray->slots + idarray->size++, namearraySo);
462     g->gc->GCWriteNew(idarray, namearraySo); // we know namearraySo is white so we can use GCWriteNew
463 
464     PyrObject* idarrayDe = newPyrArray(g->gc, numDst * sizeof(SInt32), 0, true);
465     SetObject(idarray->slots + idarray->size++, idarrayDe);
466     g->gc->GCWriteNew(idarray, idarrayDe); // we know idarrayDe is white so we can use GCWriteNew
467 
468     PyrObject* namearrayDe = newPyrArray(g->gc, numDst * sizeof(PyrObject), 0, true);
469     SetObject(idarray->slots + idarray->size++, namearrayDe);
470     g->gc->GCWriteNew(idarray, namearrayDe); // we know namearrayDe is white so we can use GCWriteNew
471 
472     PyrObject* devarrayDe = newPyrArray(g->gc, numDst * sizeof(PyrObject), 0, true);
473     SetObject(idarray->slots + idarray->size++, devarrayDe);
474     g->gc->GCWriteNew(idarray, devarrayDe); // we know devarrayDe is white so we can use GCWriteNew
475 
476 
477     for (int i = 0; i < numSrc; ++i) {
478         MIDIEndpointRef src = MIDIGetSource(i);
479         SInt32 id;
480         MIDIObjectGetIntegerProperty(src, kMIDIPropertyUniqueID, &id);
481 
482         MIDIEntityRef ent;
483         error = MIDIEndpointGetEntity(src, &ent);
484 
485         CFStringRef devname, endname;
486         char cendname[1024], cdevname[1024];
487 
488         // Virtual sources don't have entities
489         if (error) {
490             MIDIObjectGetStringProperty(src, kMIDIPropertyName, &devname);
491             MIDIObjectGetStringProperty(src, kMIDIPropertyName, &endname);
492             if (devname)
493                 CFStringGetCString(devname, cdevname, 1024, kCFStringEncodingUTF8);
494             if (endname)
495                 CFStringGetCString(endname, cendname, 1024, kCFStringEncodingUTF8);
496         } else {
497             MIDIDeviceRef dev;
498 
499             MIDIEntityGetDevice(ent, &dev);
500             MIDIObjectGetStringProperty(dev, kMIDIPropertyName, &devname);
501             MIDIObjectGetStringProperty(src, kMIDIPropertyName, &endname);
502             if (devname)
503                 CFStringGetCString(devname, cdevname, 1024, kCFStringEncodingUTF8);
504             if (endname)
505                 CFStringGetCString(endname, cendname, 1024, kCFStringEncodingUTF8);
506         }
507 
508         PyrString* string = newPyrString(g->gc, cendname, 0, true);
509         SetObject(namearraySo->slots + i, string);
510         namearraySo->size++;
511         g->gc->GCWriteNew(namearraySo, (PyrObject*)string); // we know string is white so we can use GCWriteNew
512 
513         PyrString* devstring = newPyrString(g->gc, cdevname, 0, true);
514         SetObject(devarraySo->slots + i, devstring);
515         devarraySo->size++;
516         g->gc->GCWriteNew(devarraySo, (PyrObject*)devstring); // we know devString is white so we can use GCWriteNew
517 
518         SetInt(idarraySo->slots + i, id);
519         idarraySo->size++;
520 
521         if (devname)
522             CFRelease(devname);
523         if (endname)
524             CFRelease(endname);
525     }
526 
527 
528     //      post("numDst %d\n",  numDst);
529     for (int i = 0; i < numDst; ++i) {
530         MIDIEndpointRef dst = MIDIGetDestination(i);
531         SInt32 id;
532         MIDIObjectGetIntegerProperty(dst, kMIDIPropertyUniqueID, &id);
533 
534         MIDIEntityRef ent;
535         error = MIDIEndpointGetEntity(dst, &ent);
536 
537         CFStringRef devname, endname;
538         char cendname[1024], cdevname[1024];
539 
540         // Virtual destinations don't have entities either
541         if (error) {
542             MIDIObjectGetStringProperty(dst, kMIDIPropertyName, &devname);
543             MIDIObjectGetStringProperty(dst, kMIDIPropertyName, &endname);
544             if (devname)
545                 CFStringGetCString(devname, cdevname, 1024, kCFStringEncodingUTF8);
546             if (endname)
547                 CFStringGetCString(endname, cendname, 1024, kCFStringEncodingUTF8);
548 
549         } else {
550             MIDIDeviceRef dev;
551 
552             MIDIEntityGetDevice(ent, &dev);
553             MIDIObjectGetStringProperty(dev, kMIDIPropertyName, &devname);
554             MIDIObjectGetStringProperty(dst, kMIDIPropertyName, &endname);
555             if (devname)
556                 CFStringGetCString(devname, cdevname, 1024, kCFStringEncodingUTF8);
557             if (endname)
558                 CFStringGetCString(endname, cendname, 1024, kCFStringEncodingUTF8);
559         }
560 
561         PyrString* string = newPyrString(g->gc, cendname, 0, true);
562         SetObject(namearrayDe->slots + namearrayDe->size++, string);
563         g->gc->GCWriteNew(namearrayDe, (PyrObject*)string); // we know string is white so we can use GCWriteNew
564 
565         PyrString* devstring = newPyrString(g->gc, cdevname, 0, true);
566 
567         SetObject(devarrayDe->slots + devarrayDe->size++, devstring);
568         g->gc->GCWriteNew(devarrayDe, (PyrObject*)devstring); // we know devstring is white so we can use GCWriteNew
569 
570         SetInt(idarrayDe->slots + idarrayDe->size++, id);
571 
572         if (devname)
573             CFRelease(devname);
574         if (endname)
575             CFRelease(endname);
576     }
577     return errNone;
578 }
579 
580 
581 int prConnectMIDIIn(struct VMGlobals* g, int numArgsPushed);
prConnectMIDIIn(struct VMGlobals * g,int numArgsPushed)582 int prConnectMIDIIn(struct VMGlobals* g, int numArgsPushed) {
583     PyrSlot* inputIndexSlot = g->sp - 1;
584     PyrSlot* uidSlot = g->sp;
585 
586     int err, inputIndex, uid;
587 
588     err = slotIntVal(inputIndexSlot, &inputIndex);
589     if (err)
590         return errWrongType;
591     if (inputIndex < 0 || inputIndex >= gNumMIDIInPorts)
592         return errIndexOutOfRange;
593 
594     err = slotIntVal(uidSlot, &uid);
595     if (err)
596         return errWrongType;
597 
598     MIDIEndpointRef src = 0;
599     MIDIObjectType mtype;
600     MIDIObjectFindByUniqueID(uid, &src, &mtype);
601     if (mtype != kMIDIObjectType_Source)
602         return errFailed;
603 
604     // MIDIPortConnectSource's third parameter is just a unique value used to help
605     // identify the source. It expects a void*, so we reinterpret_cast from uid to
606     // avoid a compiler warning.
607     // See: https://stackoverflow.com/questions/9051292/midireadproc-using-srcconnrefcon-to-listen-to-only-one-source
608     MIDIPortConnectSource(gMIDIInPort[inputIndex], src, reinterpret_cast<void*>(uid));
609 
610     return errNone;
611 }
612 
613 int prDisconnectMIDIIn(struct VMGlobals* g, int numArgsPushed);
prDisconnectMIDIIn(struct VMGlobals * g,int numArgsPushed)614 int prDisconnectMIDIIn(struct VMGlobals* g, int numArgsPushed) {
615     PyrSlot* b = g->sp - 1;
616     PyrSlot* c = g->sp;
617 
618     int err, inputIndex, uid;
619     err = slotIntVal(b, &inputIndex);
620     if (err)
621         return err;
622     if (inputIndex < 0 || inputIndex >= gNumMIDIInPorts)
623         return errIndexOutOfRange;
624     err = slotIntVal(c, &uid);
625     if (err)
626         return err;
627 
628     MIDIEndpointRef src = 0;
629     MIDIObjectType mtype;
630     MIDIObjectFindByUniqueID(uid, &src, &mtype);
631     if (mtype != kMIDIObjectType_Source)
632         return errFailed;
633 
634     MIDIPortDisconnectSource(gMIDIInPort[inputIndex], src);
635 
636     return errNone;
637 }
638 int prInitMIDI(struct VMGlobals* g, int numArgsPushed);
prInitMIDI(struct VMGlobals * g,int numArgsPushed)639 int prInitMIDI(struct VMGlobals* g, int numArgsPushed) {
640     // PyrSlot *a = g->sp - 2;
641     PyrSlot* b = g->sp - 1;
642     PyrSlot* c = g->sp;
643 
644     int err, numIn, numOut;
645     err = slotIntVal(b, &numIn);
646     if (err)
647         return errWrongType;
648 
649     err = slotIntVal(c, &numOut);
650     if (err)
651         return errWrongType;
652 
653     return initMIDI(numIn, numOut);
654 }
655 int prDisposeMIDIClient(VMGlobals* g, int numArgsPushed);
prDisposeMIDIClient(VMGlobals * g,int numArgsPushed)656 int prDisposeMIDIClient(VMGlobals* g, int numArgsPushed) { return midiCleanUp(); }
657 int prRestartMIDI(VMGlobals* g, int numArgsPushed);
prRestartMIDI(VMGlobals * g,int numArgsPushed)658 int prRestartMIDI(VMGlobals* g, int numArgsPushed) {
659     MIDIRestart();
660     return errNone;
661 }
662 
663 void freeSysex(MIDISysexSendRequest* pk);
freeSysex(MIDISysexSendRequest * pk)664 void freeSysex(MIDISysexSendRequest* pk) { free(pk); }
665 
666 
667 int prSendSysex(VMGlobals* g, int numArgsPushed);
prSendSysex(VMGlobals * g,int numArgsPushed)668 int prSendSysex(VMGlobals* g, int numArgsPushed) {
669     int err, uid, size;
670 
671     if (!isKindOfSlot(g->sp, s_int8array->u.classobj))
672         return errWrongType;
673 
674     PyrInt8Array* packet = slotRawInt8Array(g->sp);
675     size = packet->size;
676 
677     PyrSlot* u = g->sp - 1;
678     err = slotIntVal(u, &uid);
679     if (err)
680         return err;
681 
682     MIDIEndpointRef dest;
683     MIDIObjectType mtype;
684     MIDIObjectFindByUniqueID(uid, (MIDIObjectRef*)&dest, &mtype);
685     if (mtype != kMIDIObjectType_Destination)
686         return errFailed;
687     if (!dest)
688         return errFailed;
689 
690     MIDISysexSendRequest* pk = (MIDISysexSendRequest*)malloc(sizeof(MIDISysexSendRequest) + size);
691     Byte* data = (Byte*)pk + sizeof(MIDISysexSendRequest);
692 
693     memcpy(data, packet->b, size);
694     pk->complete = false;
695     pk->destination = dest;
696     pk->data = data;
697     pk->bytesToSend = size;
698     pk->completionProc = freeSysex;
699     pk->completionRefCon = 0;
700 
701     return ((MIDISendSysex(pk) == (OSStatus)0) ? errNone : errFailed);
702 }
703 
704 #if SC_IPHONE
705 
machTimebaseInfo()706 static struct mach_timebase_info machTimebaseInfo() {
707     struct mach_timebase_info info;
708     mach_timebase_info(&info);
709     return info;
710 }
711 
midiTime(float latencySeconds)712 static MIDITimeStamp midiTime(float latencySeconds) {
713     // add the latency expressed in seconds, to the current host time base.
714     static struct mach_timebase_info info = machTimebaseInfo(); // cache the timebase info.
715     Float64 latencyNanos = 1000000000 * latencySeconds;
716     MIDITimeStamp latencyMIDI = (latencyNanos / (Float64)info.numer) * (Float64)info.denom;
717     return (MIDITimeStamp)mach_absolute_time() + latencyMIDI;
718 }
719 
720 #else
721 
midiTime(float latencySeconds)722 static MIDITimeStamp midiTime(float latencySeconds) {
723     // add the latency expressed in seconds, to the current host time base.
724     UInt64 latencyNanos = 1000000000 * latencySeconds; // secs to nano
725     return (MIDITimeStamp)AudioGetCurrentHostTime() + AudioConvertNanosToHostTime(latencyNanos);
726 }
727 
728 #endif
729 
730 void sendmidi(int port, MIDIEndpointRef dest, int length, int hiStatus, int loStatus, int aval, int bval, float late);
sendmidi(int port,MIDIEndpointRef dest,int length,int hiStatus,int loStatus,int aval,int bval,float late)731 void sendmidi(int port, MIDIEndpointRef dest, int length, int hiStatus, int loStatus, int aval, int bval, float late) {
732     MIDIPacketList mpktlist;
733     MIDIPacketList* pktlist = &mpktlist;
734     MIDIPacket* pk = MIDIPacketListInit(pktlist);
735     ByteCount nData = (ByteCount)length;
736     pk->data[0] = (Byte)(hiStatus & 0xF0) | (loStatus & 0x0F);
737     pk->data[1] = (Byte)aval;
738     pk->data[2] = (Byte)bval;
739     pk = MIDIPacketListAdd(pktlist, sizeof(struct MIDIPacketList), pk, midiTime(late), nData, pk->data);
740     /*OSStatus error =*/MIDISend(gMIDIOutPort[port], dest, pktlist);
741 }
742 
743 int prSendMIDIOut(struct VMGlobals* g, int numArgsPushed);
prSendMIDIOut(struct VMGlobals * g,int numArgsPushed)744 int prSendMIDIOut(struct VMGlobals* g, int numArgsPushed) {
745     // port, uid, len, hiStatus, loStatus, a, b, latency
746     // PyrSlot *m = g->sp - 8;
747     PyrSlot* p = g->sp - 7;
748 
749     PyrSlot* u = g->sp - 6;
750     PyrSlot* l = g->sp - 5;
751 
752     PyrSlot* his = g->sp - 4;
753     PyrSlot* los = g->sp - 3;
754 
755     PyrSlot* a = g->sp - 2;
756     PyrSlot* b = g->sp - 1;
757     PyrSlot* plat = g->sp;
758 
759 
760     int err, outputIndex, uid, length, hiStatus, loStatus, aval, bval;
761     float late;
762     err = slotIntVal(p, &outputIndex);
763     if (err)
764         return err;
765     if (outputIndex < 0 || outputIndex >= gNumMIDIInPorts)
766         return errIndexOutOfRange;
767 
768     err = slotIntVal(u, &uid);
769     if (err)
770         return err;
771     err = slotIntVal(l, &length);
772     if (err)
773         return err;
774     err = slotIntVal(his, &hiStatus);
775     if (err)
776         return err;
777     err = slotIntVal(los, &loStatus);
778     if (err)
779         return err;
780     err = slotIntVal(a, &aval);
781     if (err)
782         return err;
783     err = slotIntVal(b, &bval);
784     if (err)
785         return err;
786     err = slotFloatVal(plat, &late);
787     if (err)
788         return err;
789 
790     MIDIEndpointRef dest;
791     MIDIObjectType mtype;
792     MIDIObjectFindByUniqueID(uid, (MIDIObjectRef*)&dest, &mtype);
793     if (mtype != kMIDIObjectType_Destination)
794         return errFailed;
795 
796     if (!dest)
797         return errFailed;
798 
799     sendmidi(outputIndex, dest, length, hiStatus, loStatus, aval, bval, late);
800     return errNone;
801 }
802 
803 // not needed in CoreMIDI:
initMIDIClient()804 int initMIDIClient() { return errNone; }
805 int prInitMIDIClient(struct VMGlobals* g, int numArgsPushed);
prInitMIDIClient(struct VMGlobals * g,int numArgsPushed)806 int prInitMIDIClient(struct VMGlobals* g, int numArgsPushed) { return initMIDIClient(); }
807 //--------------
808 
initMIDIPrimitives()809 void initMIDIPrimitives() {
810     int base, index;
811 
812     base = nextPrimitiveIndex();
813     index = 0;
814     gSysexData.reserve(1024);
815 
816     s_midiin = getsym("MIDIIn");
817     s_domidiaction = getsym("doAction");
818     s_midiNoteOnAction = getsym("doNoteOnAction");
819     s_midiNoteOffAction = getsym("doNoteOffAction");
820     s_midiTouchAction = getsym("doTouchAction");
821     s_midiControlAction = getsym("doControlAction");
822     s_midiPolyTouchAction = getsym("doPolyTouchAction");
823     s_midiProgramAction = getsym("doProgramAction");
824     s_midiBendAction = getsym("doBendAction");
825     s_midiSysexAction = getsym("doSysexAction");
826     s_midiInvalidSysexAction = getsym("doInvalidSysexAction"); // client can handle incorrect case
827     s_midiSysrtAction = getsym("doSysrtAction");
828     s_midiSMPTEAction = getsym("doSMPTEaction");
829     s_numMIDIDev = getsym("prSetNumberOfDevices");
830     s_midiclient = getsym("MIDIClient");
831 
832     definePrimitive(base, index++, "_ListMIDIEndpoints", prListMIDIEndpoints, 1, 0);
833     definePrimitive(base, index++, "_InitMIDI", prInitMIDI, 3, 0);
834     definePrimitive(base, index++, "_InitMIDIClient", prInitMIDIClient, 1, 0);
835     definePrimitive(base, index++, "_ConnectMIDIIn", prConnectMIDIIn, 3, 0);
836     definePrimitive(base, index++, "_DisconnectMIDIIn", prDisconnectMIDIIn, 3, 0);
837     definePrimitive(base, index++, "_DisposeMIDIClient", prDisposeMIDIClient, 1, 0);
838     definePrimitive(base, index++, "_RestartMIDI", prRestartMIDI, 1, 0);
839     definePrimitive(base, index++, "_SendMIDIOut", prSendMIDIOut, 9, 0);
840     definePrimitive(base, index++, "_SendSysex", prSendSysex, 3, 0);
841     if (gMIDIClient)
842         midiCleanUp();
843 }
844