//| Copyright: (C) 2009-2020 Kevin Larke //| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file. #include #include #include #include #include "cmPrefix.h" #include "cmGlobal.h" #include "cmRpt.h" #include "cmErr.h" #include "cmCtx.h" #include "cmMem.h" #include "cmMallocDebug.h" #include "cmTime.h" #include "cmMidi.h" #include "cmMidiPort.h" typedef struct { bool inputFl; char* nameStr; SInt32 uid; MIDIEndpointRef epr; cmMpParserH_t parserH; double prevMicroSecs; } cmMpPort; typedef struct { char* nameStr; unsigned devId; // this device is known to OSX by this id unsigned devIdx; // this device is known externally by this device index (also index into cmMpRoot.devArray[]) unsigned iPortCnt; cmMpPort* iPortArray; unsigned oPortCnt; cmMpPort* oPortArray; } cmMpDev; typedef struct { cmErr_t err; unsigned devCnt; cmMpDev* devArray; cmMpCallback_t cbFunc; void* cbDataPtr; MIDIClientRef clientRef; MIDIPortRef inPortRef; MIDIPortRef outPortRef; mach_timebase_info_data_t timeBaseInfo; } cmMpRoot; cmMpRoot _cmMpRoot; cmMpRoot* _cmMpRtPtr = NULL; cmMpRC_t _cmMpError( cmErr_t* errPtr, cmMpRC_t rc, OSStatus err, const char* fmt, ... ) { va_list vl0; va_list vl1; va_start(vl0,fmt); va_copy(vl1,vl0); unsigned n0 = vsnprintf(NULL,0,fmt,vl0); unsigned n1 = 0; unsigned n2 = 0; const char* fmt2 = "OS Status:%i"; va_end(vl0); if( err != noErr ) n1 = snprintf(NULL,0,fmt2,err); unsigned bufCharCnt = n0 + n1; char buf[ bufCharCnt + 1]; vsnprintf(buf,n0,fmt,vl1); n2 = strlen(buf); snprintf(buf+n2,bufCharCnt-n2,fmt2,err); buf[bufCharCnt]=0; cmErrMsg(errPtr,rc,buf); va_end(vl1); return rc; } char* _cmMpAllocStringProperty( cmErr_t* errPtr, MIDIObjectRef obj, CFStringRef propId ) { OSStatus err; CFStringRef cfStrRef = NULL; char* strPtr = NULL; if(( err = MIDIObjectGetStringProperty(obj,propId,&cfStrRef)) != noErr ) { _cmMpError(errPtr,kCfStringErrMpRC,err,"Get property failed."); return NULL; } unsigned charCnt = CFStringGetLength( cfStrRef ) + 1; if( charCnt > 0 ) { strPtr = cmMemAllocZ( char, charCnt ); if( !CFStringGetCString( cfStrRef, strPtr, charCnt, kCFStringEncodingASCII)) { _cmMpError(errPtr,kCfStringErrMpRC,noErr,"CFstring to cstring failed."); cmMemFree(strPtr); strPtr = NULL; } } CFRelease(cfStrRef); return strPtr; } void _cmMpDeviceInit( cmMpDev* drp ) { drp->nameStr = NULL; drp->iPortCnt = 0; drp->oPortCnt = 0; drp->iPortArray = NULL; drp->oPortArray = NULL; } void _cmMpDeviceFree( cmMpDev* drp ) { unsigned i; for(i=0; iiPortCnt; ++i) { cmMemPtrFree(&drp->iPortArray[i].nameStr); cmMpParserDestroy( &drp->iPortArray[i].parserH ); } for(i=0; ioPortCnt; ++i) cmMemPtrFree(&drp->oPortArray[i].nameStr); cmMemPtrFree(&drp->iPortArray); cmMemPtrFree(&drp->oPortArray); cmMemPtrFree(&drp->nameStr); _cmMpDeviceInit(drp); } void _cmMpDevicePrint( cmMpDev* drp, unsigned devIdx, cmRpt_t* rpt ) { unsigned i,j; cmRptPrintf(rpt,"%i '%s'\n",devIdx,drp->nameStr); for(j=0; j<2; ++j) { unsigned portCnt = j==0 ? drp->iPortCnt : drp->oPortCnt; for(i=0; iiPortArray + i : drp->oPortArray + i ; cmRptPrintf(rpt," port:%s %i id:0x:%x '%s'\n", p->inputFl ? "in " : "out",i, p->uid, p->nameStr ); } } } char* _cmMpFormLabel( cmErr_t* errPtr, MIDIObjectRef obj ) { char* modelStr = _cmMpAllocStringProperty( errPtr, obj, kMIDIPropertyManufacturer ); char* nameStr = _cmMpAllocStringProperty( errPtr, obj, kMIDIPropertyModel ); char* labelStr = cmMemAllocZ( char, strlen(modelStr) + strlen(nameStr) + 4 ); strcpy(labelStr,modelStr); strcat(labelStr," - "); strcat(labelStr,nameStr); cmMemFree(modelStr); cmMemFree(nameStr); return labelStr; } char* _cmMpFormPortLabel( cmErr_t* errPtr, MIDIEntityRef mer, ItemCount pi, bool inputFl ) { MIDIEndpointRef epr; if( inputFl ) epr = MIDIEntityGetSource( mer, pi); else epr = MIDIEntityGetDestination( mer, pi); if(epr == 0 ) { _cmMpError(errPtr,kSysErrMpRC,noErr,"Get %s endpoiint ref %i failed.", inputFl ? "source" : "destination",pi); return NULL; } return _cmMpAllocStringProperty( errPtr, (MIDIObjectRef)epr, kMIDIPropertyName ); } // Fill id array with the unique id of each endpoint or 0 if the endpoint is offline. cmMpRC_t _cmMpGetEntityUniqueIdArray( MIDIEntityRef mer, SInt32* idArray, unsigned idCnt, bool inputFl, ItemCount* activeCntPtr, cmErr_t* errPtr ) { cmMpRC_t rc = kOkMpRC; MIDIEndpointRef epr; unsigned pi; char* dirLabel = inputFl ? "source" : "dest"; SInt32 offline; OSStatus err; *activeCntPtr = 0; for(pi=0; pirpt ); } portArray[pi].uid = idArray[pi]; portArray[pi].inputFl = inputFl; portArray[pi].nameStr = _cmMpFormPortLabel(errPtr, mer, pi, inputFl ); portArray[pi].epr = epr; portArray[pi].prevMicroSecs = 0; } return rc; } cmMpRC_t _cmMpGetActiveEntityPortCount( MIDIEntityRef mer, bool inputFl, ItemCount* portCntPtr, ItemCount* activePortCntPtr, cmErr_t* errPtr ) { *portCntPtr = inputFl ? MIDIEntityGetNumberOfSources( mer ) : MIDIEntityGetNumberOfDestinations( mer ); SInt32 idArray[ *portCntPtr ]; return _cmMpGetEntityUniqueIdArray( mer, idArray, *portCntPtr, inputFl, activePortCntPtr, errPtr ); } cmMpRC_t _cmMpIsDeviceActive( unsigned devIdx, MIDIDeviceRef mdr, ItemCount* srcCntPtr, ItemCount* dstCntPtr, bool* activeFlPtr, cmErr_t* errPtr ) { cmMpRC_t rc = kOkMpRC; ItemCount entityCnt = MIDIDeviceGetNumberOfEntities(mdr); unsigned ei; *srcCntPtr = 0; *dstCntPtr = 0; *activeFlPtr = false; for(ei=0; eiiPortCnt = devSrcCnt; drp->iPortArray = cmMemAllocZ( cmMpPort, devSrcCnt ); // allocate the output port array drp->oPortCnt = devDstCnt; drp->oPortArray = cmMemAllocZ( cmMpPort, devDstCnt ); // form the device name string drp->nameStr = _cmMpFormLabel( errPtr, (MIDIObjectRef)mdr); drp->devId = deviceId; drp->devIdx = deviceIdx; unsigned entityCnt = MIDIDeviceGetNumberOfEntities(mdr); unsigned iPortCnt = 0; unsigned oPortCnt = 0; // for each entity on this device for(ei=0; eiiPortArray : drp->oPortArray; ItemCount entityPortCnt = inputFl ? MIDIEntityGetNumberOfSources( mer ) : MIDIEntityGetNumberOfDestinations( mer ); SInt32 idArray[ entityPortCnt ]; // fill idArray with the unique id's of the active ports on this entity if((rc = _cmMpGetEntityUniqueIdArray(mer,idArray,entityPortCnt,inputFl,&activeCnt,errPtr)) != kOkMpRC ) { rc = _cmMpError(errPtr,rc,noErr,"Unable to locate unique source ids on device:%i entity:%i",deviceId,ei); goto errLabel; } // assign the unique id's to each cmMpPort assoc'd with this entity _cmMpInitPortArray(deviceIdx, inPortRef, mer, portArray + portCnt, idArray, entityPortCnt, inputFl, cbFunc, cbDataPtr, parserBufByteCnt, errPtr ); if( inputFl ) iPortCnt += entityPortCnt; else oPortCnt += entityPortCnt; //printf("OSX MIDI %i %i %i %s\n",deviceId,ei,i,drp->nameStr); } } assert( iPortCnt == devSrcCnt ); assert( oPortCnt == devDstCnt ); errLabel: if( rc != kOkMpRC ) _cmMpDeviceFree(drp); return rc; } void _cmMpDestroyDeviceArray( cmMpRoot* rp ) { unsigned i; for(i=0; idevCnt; ++i) _cmMpDeviceFree( rp->devArray + i ); cmMemPtrFree(&rp->devArray); rp->devCnt = 0; } cmMpRC_t _cmMpCreateDeviceArray( cmMpRoot* rp, unsigned parserBufByteCnt ) { cmMpRC_t rc = kOkMpRC; ItemCount devCnt = MIDIGetNumberOfDevices(); ItemCount di = 0; rp->devCnt = 0; rp->devArray = cmMemAllocZ( cmMpDev, devCnt ); for(di=0; didevArray + rp->devCnt; // attempt to create a device record for device di if((rc = _cmMpCreateDevice( di, rp->devCnt, drp, rp->inPortRef, rp->cbFunc, rp->cbDataPtr, parserBufByteCnt, &rp->err )) != kOkMpRC ) goto errLabel; // if the device di is active if( drp->iPortCnt || drp->oPortCnt ) ++rp->devCnt; } errLabel: if( rc != kOkMpRC ) _cmMpDestroyDeviceArray(rp); return rc; } void _cmMpMidiSystemNotifyProc( const MIDINotification* notifyMsgPtr, void *refCon ) { // cmMpRoot* p = (mdRoot*)refCon; // if(p != NULL ) // ((fw::osxMidiPorts*)refCon)->onNotify(notifyMsgPtr); } void _cmMpMIDISystemReadProc( const MIDIPacketList *pktListPtr, void* readProcRefCon, void* srcConnRefCon ) { //cmMpRoot* rp = (cmMpRoot*)readProcRefCon; cmMpPort* pp = (cmMpPort*)srcConnRefCon; unsigned i; if( pktListPtr == NULL /*|| rp == NULL*/ || pp == NULL /*|| rp->cbFunc == NULL*/ ) return; const MIDIPacket* packetPtr = &pktListPtr->packet[0]; for(i=0; i < pktListPtr->numPackets; ++i) { /* unsigned j; for(j=0; jlength; ++j) printf("0x%x ", packetPtr->data[j]); printf("\n"); */ // About converting mach_absolute_time() to seconds: http://lists.apple.com/archives/carbon-dev/2007/Jan//msg00282.html // About MIDIPacket.timeStamp: http://developer.apple.com/library/ios/#documentation/CoreMidi/Reference/MIDIServices_Reference/Reference/reference.html double nano = 1e-9 * ( (double) _cmMpRoot.timeBaseInfo.numer) / ((double) _cmMpRoot.timeBaseInfo.denom); // so here's the timestamp in nanoseconds: double nanoSeconds = ((double) packetPtr->timeStamp) * nano; // 1000 times that for microSeconds: double microSecs = 1000.0f * nanoSeconds; // BUG BUG BUG: How can multiplying the nanoseconds produce microseconds? // Shouldn't the nano to micro conversion be a divide? //double deltaMicroSecs = microSecs - pp->prevMicroSecs; pp->prevMicroSecs = microSecs; cmTimeSpec_t ts; ts.tv_sec = floor(microSecs / 1000000.0); ts.tv_nsec = (microSecs - ts.tv_sec * 1000000.0) * 1000.0; assert( pp->inputFl == true ); cmMpParseMidiData( pp->parserH, &ts, packetPtr->data, packetPtr->length ); packetPtr = MIDIPacketNext(packetPtr); } } cmMpPort* _cmMpGetPort( cmMpDev* drp, unsigned portIdx, unsigned flags ) { assert( flags != 0 ); bool inputFl = cmIsFlag(flags, kInMpFl); assert( portIdx < (inputFl ? drp->iPortCnt : drp->oPortCnt)); return inputFl ? drp->iPortArray + portIdx : drp->oPortArray + portIdx; } cmMpRC_t cmMpInitialize( cmCtx_t* c, cmMpCallback_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt, const char* appNameStr ) { cmMpRC_t rc = kOkMpRC; OSStatus err = noErr; CFStringRef clientNameStrRef = NULL; CFStringRef inPortNameStrRef = NULL; CFStringRef outPortNameStrRef = NULL; cmRpt_t* rpt = &c->rpt; if( _cmMpRtPtr == NULL ) { memset(&_cmMpRoot,0,sizeof(_cmMpRoot)); _cmMpRtPtr = &_cmMpRoot; } if((rc = cmMpFinalize()) != kOkMpRC ) return rc; cmErrSetup(&_cmMpRoot.err,rpt,"MIDI Port"); _cmMpRoot.cbFunc = cbFunc; _cmMpRoot.cbDataPtr = cbDataPtr; if(( clientNameStrRef = CFStringCreateWithCString(NULL,"MIDI",kCFStringEncodingASCII)) == NULL ) { rc = _cmMpError(&_cmMpRoot.err,kCfStringErrMpRC,noErr,"Client name create failed."); goto errLabel; } if(( err = MIDIClientCreate( clientNameStrRef, _cmMpMidiSystemNotifyProc, &_cmMpRoot, &_cmMpRoot.clientRef )) != noErr ) { rc = _cmMpError(&_cmMpRoot.err,kSysErrMpRC,err,"Client create failed."); goto errLabel; } if((inPortNameStrRef = CFStringCreateWithCString(NULL,"in",kCFStringEncodingASCII)) == NULL ) { rc = _cmMpError(&_cmMpRoot.err,kCfStringErrMpRC,noErr,"In port name string create failed."); goto errLabel; } if((err = MIDIInputPortCreate( _cmMpRoot.clientRef, inPortNameStrRef, _cmMpMIDISystemReadProc, &_cmMpRoot, &_cmMpRoot.inPortRef)) != noErr ) { rc = _cmMpError(&_cmMpRoot.err,kSysErrMpRC,err,"In port create failed."); goto errLabel; } if((outPortNameStrRef = CFStringCreateWithCString(NULL,"out",kCFStringEncodingASCII)) == NULL ) { rc = _cmMpError(&_cmMpRoot.err,kCfStringErrMpRC,noErr,"Out port name string create failed."); goto errLabel; } if((err = MIDIOutputPortCreate( _cmMpRoot.clientRef, outPortNameStrRef, &_cmMpRoot.outPortRef)) != noErr ) { rc = _cmMpError(&_cmMpRoot.err,kSysErrMpRC,err,"Out port create failed."); goto errLabel; } if((rc = _cmMpCreateDeviceArray(&_cmMpRoot,parserBufByteCnt)) != kOkMpRC ) goto errLabel; mach_timebase_info(&_cmMpRoot.timeBaseInfo); errLabel: if( clientNameStrRef != NULL ) CFRelease(clientNameStrRef); if( inPortNameStrRef != NULL ) CFRelease(inPortNameStrRef); if( outPortNameStrRef != NULL ) CFRelease(outPortNameStrRef); if( rc != kOkMpRC ) cmMpFinalize(); return rc; } cmMpRC_t cmMpFinalize() { OSStatus err = noErr; cmMpRC_t rc = kOkMpRC; if( _cmMpRoot.inPortRef != 0 ) { if((err = MIDIPortDispose(_cmMpRoot.inPortRef)) != noErr ) rc = _cmMpError(&_cmMpRoot.err,kSysErrMpRC,err,"MIDIPortDispose() failed on the input port."); else _cmMpRoot.inPortRef = 0; } if( _cmMpRoot.outPortRef != 0 ) { if((err = MIDIPortDispose(_cmMpRoot.outPortRef)) != noErr ) rc = _cmMpError(&_cmMpRoot.err,kSysErrMpRC,err,"MIDIPortDispose() failed on the output port."); else _cmMpRoot.outPortRef = 0; } if( _cmMpRoot.clientRef != 0 ) { if((err = MIDIClientDispose(_cmMpRoot.clientRef)) != noErr ) rc = _cmMpError(&_cmMpRoot.err,kSysErrMpRC,err,"MIDIClientDispose() failed."); else _cmMpRoot.clientRef = 0; } _cmMpDestroyDeviceArray(&_cmMpRoot); _cmMpRoot.devCnt = 0; _cmMpRoot.devArray = NULL; _cmMpRoot.cbFunc = NULL; _cmMpRoot.cbDataPtr = NULL; _cmMpRoot.clientRef = 0; _cmMpRoot.inPortRef = 0; _cmMpRoot.outPortRef = 0; return rc; } bool cmMpIsInitialized() { return _cmMpRoot.clientRef != 0; } unsigned cmMpDeviceCount() { return _cmMpRoot.devCnt; } const char* cmMpDeviceName( unsigned devIdx ) { assert( devIdx < _cmMpRoot.devCnt ); return _cmMpRoot.devArray[devIdx].nameStr; } unsigned cmMpDevicePortCount( unsigned devIdx, unsigned flags ) { assert( devIdx < _cmMpRoot.devCnt ); return cmIsFlag(flags,kInMpFl) ? _cmMpRoot.devArray[devIdx].iPortCnt : _cmMpRoot.devArray[devIdx].oPortCnt; } const char* cmMpDevicePortName( unsigned devIdx, unsigned flags, unsigned portIdx ) { assert( devIdx < _cmMpRoot.devCnt ); cmMpDev* drp = _cmMpRoot.devArray + devIdx; assert( portIdx < (cmIsFlag(flags,kInMpFl) ? drp->iPortCnt : drp->oPortCnt)); return _cmMpGetPort( drp, portIdx, flags )->nameStr; } cmMpRC_t cmMpDeviceSend( unsigned devIdx, unsigned portIdx, cmMidiByte_t status, cmMidiByte_t d0, cmMidiByte_t d1 ) { assert( devIdx < _cmMpRoot.devCnt ); cmMpDev* drp = _cmMpRoot.devArray + devIdx; cmMpPort* pp = _cmMpGetPort( drp, portIdx, kOutMpFl); OSStatus err = noErr; MIDIPacketList mpl; unsigned byteCnt = cmMidiStatusToByteCount(status & 0xf0 ); if( byteCnt == 0 ) return _cmMpError(&_cmMpRoot.err,kInvalidArgMpRC,noErr,"Invalid status byte (0x%x) on send to device:%i port:%i",status,devIdx,portIdx); mpl.numPackets = 1; mpl.packet[0].timeStamp = 0; mpl.packet[0].length = byteCnt+1; mpl.packet[0].data[0] = status; mpl.packet[0].data[1] = d0; mpl.packet[0].data[2] = d1; //printf("%i %i %i %i\n",status,d0,d1,byteCnt); if(( err = MIDISend( _cmMpRoot.outPortRef, pp->epr, &mpl)) != noErr ) return _cmMpError(&_cmMpRoot.err,kSysErrMpRC,err,"Send on device:%i port:%i failed.",devIdx,portIdx); return kOkMpRC; } cmMpRC_t cmMpDeviceSendData( unsigned devIdx, unsigned portIdx, const cmMidiByte_t* dataPtr, unsigned byteCnt ) { assert( devIdx < _cmMpRoot.devCnt ); cmMpDev* drp = _cmMpRoot.devArray + devIdx; cmMpPort* pp = _cmMpGetPort( drp, portIdx, kOutMpFl); OSStatus err = noErr; MIDIPacketList mpl; unsigned i; if( byteCnt > 256 ) return _cmMpError(&_cmMpRoot.err,kInvalidArgMpRC,noErr,"mdDeviceSendData() only support data buffers up to 256 bytes."); mpl.numPackets = 1; mpl.packet[0].timeStamp = 0; mpl.packet[0].length = byteCnt; for(i=0; iepr, &mpl)) != noErr ) return _cmMpError(&_cmMpRoot.err,kSysErrMpRC,err,"Data Send on device:%i port:%i failed.",devIdx,portIdx); return kOkMpRC; } cmMpRC_t cmMpInstallCallback( unsigned devIdx, unsigned portIdx, cmMpCallback_t cbFunc, void* cbDataPtr ) { cmMpRC_t rc = kOkMpRC; unsigned di; unsigned dn = cmMpDeviceCount(); for(di=0; di 0 ) rc = _cmMpError(&_cmMpRoot.err,kCbNotFoundMpRC,0,"The callback was not found on any of the specified devices or ports."); errLabel: return rc; } bool cmMpUsesCallback( unsigned devIdx, unsigned portIdx, cmMpCallback_t cbFunc, void* cbDataPtr ) { unsigned di; unsigned dn = cmMpDeviceCount(); for(di=0; di