/* A quick interface to OMS 2. This is the layer between Mac-OMS and Comparser MIDI. CMP 0.95 by Pieter Suurmond, july 8, 2003. Learned a lot from file 'sqMacMIDI.OMS.c' found in the Squeak/Siren interface to the Opcode MIDI System (OMS) by Stephen T. Pope. Include this in the (CodeWarrior) project on the Mac for OMS. Also unpack 'oms20sdk28Jan98.sit.hqx' and include binary library 'Libraries:OMSGluePPC.lib' in the project. */ #include /* Standard stuff needed for sprintf. */ #include #include #include #include /* Some Mac stuff. */ #include #include #include #include #include #include #include #include #include #include /* #include */ #include #include /* Apple MIDI Libraries. */ #include "OMS.h" /* OMS definitions and structs */ #include "CMP_midi.h" /* ComParser include. */ /* Necesaary? */ #define USESROUTINEDESCRIPTORS pascal void cmpMIDIAppHook(OMSAppHookMsg *pkt, long myRefCon); /* I/O call-backprototypes. */ pascal void cmpMIDIReadHook(OMSPacket *pkt, long myRefCon); /*-------------------------------------------------------- ONLY ONE SINGLE GLOBAL: ---------*/ static CMP_MIDI_IO* gSINGE_OMS = (CMP_MIDI_IO*)0; /* We can have only ONE oms object and */ /* apparently our cmpMIDIReadHook must */ /* read from it (arg cannot be passed). */ #define kCMP_oms_maxNumPorts 8 /* Max number of I/O ports. */ #define kCMP_oms_maxNameLen 63 /* Max length of device names. */ typedef struct cmpMIDIioPort /* I/O Port struct, platform- */ { /* dependent */ char name[1+kCMP_oms_maxNameLen]; /* Port name. */ int direction; /* Port direction 1=in, 2=out, */ } cmpMIDIioPort; /* 3 = both. */ /*---------------------------------------------------- OMS Driver Internals: ---------*/ typedef struct MD_SPECIFIC_OMS /* Ptr to this data is stored in */ { /* encapsulating, more generic */ unsigned long macSignature, /* CMP_MIDI_IO struct. It will */ inSignature, /* however only be accessed here! */ outSignature; short compatMode; /* OMS compatibility mode. */ short inPortRef; /* refNum of the OMS input port. */ short outPortRef; /* refNum of the OMS output port. */ unsigned short outNodeRefs[kCMP_oms_maxNumPorts]; cmpMIDIioPort nodeNames[kCMP_oms_maxNumPorts]; /* The IO port table. */ OMSMIDIPacket smallPacket; /* Small 4-byte MIDI packet. */ OMSMIDIPacket255 largePacket; /* Large packet for SysEx data. */ /*------------------------------------------------ Interrupt-oriented platforms */ /* need their own input buffers: */ unsigned long recBuffSize; /* Size of inputbuffer in bytes. */ unsigned char* recBuffStart; /* Startaddress of inputbuffer. */ unsigned char* recBuffWrap; /* One plus last legal address. */ unsigned char* recBuffIn; /* Write new incoming data here. */ unsigned char* recBuffOut; /* Read from FIFO buffer here, */ } MD_SPECIFIC_OMS; /* Equals recBuffIn when empty. */ /*----------------------------------------------------------------------------------------*/ void listNamesMIDI(CMP_MIDI_IO* mio, FILE* dest) /* Prints names that may be used in se- */ { /* lection of input and output ports. */ int i, j; MD_SPECIFIC_OMS* omsData; if (gSINGE_OMS) /* We must have already logged in. */ { omsData = mio->specific; fprintf(dest, "\n%d MIDI interfaces available:\n", mio->numInterfaces); for (i = 0; (i < mio->numInterfaces) && (i < kCMP_oms_maxNumPorts); i++) { fprintf(dest, "%3d ", i); if (omsData->nodeNames[i].direction & 1) fprintf(dest, "i"); else fprintf(dest, " "); if (omsData->nodeNames[i].direction & 2) fprintf(dest, "o"); else fprintf(dest, " "); fprintf(dest, ": %s\n", omsData->nodeNames[i].name + 1); /* Pascal string! */ } } else sprintf(mio->rep255, "Not logged in to OMS.\n"); } /*------------------------------------------------------------------------*/ /* Sign on to OMS, register callbacks, create ports and connections, etc. */ /* This routine will not register itself IN the parent-struc, but it will */ /* however register globally (this file only) for callBacks... */ /* The size of the 'Comparser-internal' MIDI buffer is determined below. */ void* specificOpenMIDI(CMP_MIDI_IO* mio) { OSErr err; OMSAppHookUPP appHook; /* pointer to call-back function */ OMSReadHook2UPP readHook; /* pointer to call-back function */ OMSAPI (long) versionID; /* OMS driver version */ OMSConnectionParams cmpConn; /* Connection parameter structure. */ OMSNodeInfoListH nodeInfoList; int i, j, interfaces; long LMGetCurrentA5(void); char *nname; Boolean found = false; MD_SPECIFIC_OMS* result = (MD_SPECIFIC_OMS*)NULL; if (!mio) return ((void*)result); /* There's no way of reporting this error. */ if (gSINGE_OMS) /* A shame that we cannot login twice! */ { sprintf(mio->rep255, "Already logged in to OMS.\n"); return ((void*)result); } versionID = OMSVersion(); /* Check whether OMS V2 is installed. */ if ((!versionID) || ((versionID & 0xff000000) != 0x02000000)) { sprintf(mio->rep255, "Wrong OMS version: %ld.\n", versionID); return ((void*)result); } result = (MD_SPECIFIC_OMS*)malloc(sizeof(MD_SPECIFIC_OMS)); if (!result) { sprintf(mio->rep255, "Not enough memory for OMS.\n"); return ((void*)result); } /*-------------------------------------- Init native datastructure: -------------*/ for (i=0; ioutNodeRefs[i] = 0; result->macSignature = 'cmp@'; /* <<<============ CREATER TYPE */ result->inSignature = 'in '; result->outSignature = 'out '; /*---------------------------------------- Set op our own input-buffer: ---------*/ /* result->recOverflow = 0; Done by caller now. */ result->recBuffSize = 16384L; /* Size in bytes is determined here. */ result->recBuffStart = (unsigned char*)malloc(result->recBuffSize); if (!result->recBuffStart) /* From now on freeExit may be jumped to, but not */ { /* earlier. */ sprintf(mio->rep255, "not enough memory for OMS input buffer.\n"); goto freeExit; /* freeExit does no OMS-sign-out as outExit does! */ } result->recBuffIn = result->recBuffStart; result->recBuffOut = result->recBuffStart; /* Equal means empty. */ result->recBuffWrap = result->recBuffStart + result->recBuffSize; /*-------------------------------------- Set up call-back routine pointers: -----*/ appHook = NewOMSAppHook(cmpMIDIAppHook); readHook = NewOMSReadHook2(cmpMIDIReadHook); /*-------------------------------------- Sign in to OMS: ------------------------*/ err = OMSSignIn(result->macSignature, (long)LMGetCurrentA5(), (unsigned char*)mio->login31, appHook, &(result->compatMode)); if (err != omsNoErr) { sprintf(mio->rep255, "In specificOpenMIDI(), OMSSignIn() = %d.\n", err); goto freeExit; /* freeExit does no OMS-sign-out as outExit does! */ } /*-------------------------- Already register globally for readHooks.. */ gSINGE_OMS = mio; /* Copy address of comparser object globally. */ /*--------------------------- Add an input port: -------------------------*/ err = OMSAddPort(result->macSignature, result->inSignature, omsPortTypeInput, readHook, (long)LMGetCurrentA5(), &(result->inPortRef)); if (err) { sprintf(mio->rep255, "Adding input: OMSAddPort() = %d.\n", err); goto outExit; } /*--------------------------- Add output port: ---------------------------*/ err = OMSAddPort(result->macSignature, result->outSignature, omsPortTypeOutput, NULL, 0L, &(result->outPortRef)); /* Out needs no readhook. */ if (err) { sprintf(mio->rep255, "Adding output: OMSAddPort() = %d.\n", err); goto outExit; } /*----------- Set up OMS to play on all output ports: ------------------------------*/ nodeInfoList = OMSGetNodeInfo(omsIncludeOutputs + omsIncludeReal + omsIncludeVirtual); if (nodeInfoList == NULL) { sprintf(mio->rep255, "For output: OMSGetNodeInfo() = NULL.\n"); goto outExit; } interfaces = (*nodeInfoList)->numNodes; for (i=0; i<(*nodeInfoList)->numNodes; i++) { nname = (char *)(*nodeInfoList)->info[i].name; /* printf("\tOpen interface %s ", nname); */ strcpy(result->nodeNames[i].name, nname); /* Actually a pascal-string! */ result->nodeNames[i].direction = 2; result->outNodeRefs[i] = (*nodeInfoList)->info[i].ioRefNum; } OMSDisposeHandle(nodeInfoList); /*---------------- Read from all input ports: -------------------------------------*/ cmpConn.appRefCon = 0; nodeInfoList = OMSGetNodeInfo(omsIncludeInputs + omsIncludeReal + omsIncludeVirtual); if (nodeInfoList == NULL) { sprintf(mio->rep255, "For input: OMSGetNodeInfo() = NULL.\n"); goto outExit; } for (i=0; i<(*nodeInfoList)->numNodes; i++) { nname = (char *)(*nodeInfoList)->info[i].name; if ((*nodeInfoList)->info[i].flags & kAnyIsController) /* HUH? */ { cmpConn.nodeUniqueID = (*nodeInfoList)->info[i].uniqueID; err = OMSOpenConnections(result->macSignature, result->inSignature, 1, &cmpConn, false); if (err) { sprintf(mio->rep255, "OMSOpenConnections() = %d.\n", err); goto outExit; } for (j=0; jnodeNames[j].name) == 0) { result->nodeNames[j].direction += 1; found = true; } if (!found) { /* Actually a pascal-string! */ strcpy(result->nodeNames[interfaces].name, nname); result->nodeNames[interfaces].direction = 1; interfaces ++; } /* printf("\tConnect input %s ", nname); */ } } OMSDisposeHandle(nodeInfoList); /*----------------------------------------------------------------------*/ mio->numInterfaces = interfaces; /* Copy number of interfaces found. */ sprintf(mio->rep255, "Logged in to OMS, %d interfaces found.\n", interfaces); return ((void*)result); /* The calling parent will register */ outExit: /* this result-struct in 'mio'. */ OMSSignOut(result->macSignature); freeExit: if (result->recBuffStart) /* Sure ->recBuffStart is written above! */ free(result->recBuffStart); free(result); /* Sure it's there. */ result = (MD_SPECIFIC_OMS*)NULL; gSINGE_OMS = (CMP_MIDI_IO*)NULL; /* Signal globally (in this file only). */ return ((void*)result); } /*-----------------------------------------------------------------------*/ /* Close up shop and sign out from OMS. You never have to call this rou- */ /* tine directly, but call 'releaseMidiIO()' which is in CMP.midi.c. */ void specificCloseMIDI(CMP_MIDI_IO* mio) { MD_SPECIFIC_OMS* oms; if (!mio) /* Argument MUST be there! */ return; /* There's no way of */ oms = (MD_SPECIFIC_OMS*)mio->specific; /* reporting this error. */ if (!oms) { sprintf(mio->rep255, "NULL oms pointer in specificCloseMIDI().\n"); return; } if (gSINGE_OMS) { OMSSignOut(oms->macSignature); sprintf(mio->rep255, "Logged out from OMS.\n"); } else sprintf(mio->rep255, "Already logged out to OMS!\n"); /* But go on. */ if (mio != gSINGE_OMS) /* Overwrite the normal 'released'-message, warn. */ sprintf(mio->rep255, "In specificCloseMIDI() OMS registry conflict.\n"); if (oms->recBuffStart) /* Sure ->recBuffStart field is ok? */ free(oms->recBuffStart); free(oms); /* Don't free(mio) for that's done by caller. */ mio->numInterfaces = 0; /* Undo again what specificOpenMIDI() did. */ mio->specific = (void*)NULL; /* Signal back to caller / other files. */ gSINGE_OMS = (CMP_MIDI_IO*)NULL;/* Signal globally (in this file only). */ } /*------------------------------------------------------------------------------*/ /* Send a MIDI message out the output port(s). Returns 0 on succes, negative on */ /* failure (then writes error report in struct, if it's there: mio->rep255). */ int sendMIDI(CMP_MIDI_IO* mio, unsigned char* ptr, int length, int interface) { int i; MD_SPECIFIC_OMS* oms; if (!(gSINGE_OMS && mio)) /* Just for safety, we don't use the global here. */ return(-1024); oms = (MD_SPECIFIC_OMS*)(mio->specific); if (!oms) { sprintf(mio->rep255, "Missing specific data in OMS sendMIDI.\n"); return(-1025); } if (length < 4) /* Play it now (i.e., no output buffer) */ { /* Handle a "normal" packet of 3 bytes */ oms->smallPacket.flags = 0; oms->smallPacket.len = length; for (i=0; ismallPacket.data[i] = *(ptr + i); if (interface == 0) { /* Interface 0 means play out all outputs */ for (i=0; ioutNodeRefs[i] != 0) OMSWritePacket2(&(oms->smallPacket), oms->outNodeRefs[i], oms->outPortRef); } else OMSWritePacket2(&(oms->smallPacket), oms->outNodeRefs[interface - 1], oms->outPortRef); } else { /* Handle a big packet */ oms->largePacket.flags = 0; oms->largePacket.len = length; for (i=0; ilargePacket.data[i] = *(ptr + i); if (interface == 0) { /* Interface 0 means play out all outputs. */ for (i=0; ioutNodeRefs[i] != 0) OMSWritePacket2((struct OMSMIDIPacket*)&(oms->largePacket), oms->outNodeRefs[i], oms->outPortRef); /* OMSWritePacket255(&largePacket, outNodeRefs[i], outPortRef); */ } else OMSWritePacket2((struct OMSMIDIPacket*)&(oms->largePacket), oms->outNodeRefs[interface - 1], oms->outPortRef); /* OMSWritePacket255(&largePacket, outNodeRefs[interface - 1], outPortRef); */ } return(0); } /*------------------------------------------------------------------------*/ /* cmpMIDIAppHook -- Sent on OMS configuration changes; NO-OPs at present */ pascal void cmpMIDIAppHook(OMSAppHookMsg *pkt, long myRefCon) { long olda5 = SetA5(myRefCon); switch (pkt->msgType) { // What to do when compatibility mode changes? case omsMsgModeChanged: // What to do? break; // What to do if a receiver is deleted? case omsMsgDestDeleted: // What to do? break; // What to do if the studio layout changes? case omsMsgNodesChanged: // What to do? break; } SetA5(olda5); } /*------------------------------------------------------------------------*/ /* Mac OMS interrupt routine called on in-coming MIDI messages. */ /* First 2 bytes of a messages (not necessarily word-aligned!) in buffer */ /* is length (this 2 first ones exluded). */ /* When this 16 bit number is null, it means we wrap. */ /* Also when there's only 1 byte left: whether 0 or not, we wrap to start.*/ /* channel-filter / omni and sysex-manufact-ID is filtered here! */ pascal void cmpMIDIReadHook(OMSPacket *pkt, long myRefCon) { int len, i; unsigned char messType, *wr; long filled, empty, len2; long olda5 = SetA5(myRefCon); MD_SPECIFIC_OMS* oms; if (!gSINGE_OMS) /* Not logged in / object is gone. */ goto rhDone; oms = (MD_SPECIFIC_OMS*)(gSINGE_OMS->specific); if (!oms) /* It is a pity we HAVE to read */ goto rhDone; /* from that dirty global here. */ len = pkt->len; if (!len) /* Ignore empty packets. */ goto rhDone; if (gSINGE_OMS->recThru) /* Software thru for all channels. */ { /* (Even when overflown). */ for (i=0; ioutNodeRefs[i] != 0) OMSWritePacket(pkt, oms->outNodeRefs[i], oms->outPortRef); } if (gSINGE_OMS->recOverflow) /* Buffer already overflown. */ goto rhDone; /* Don't record any more packets. */ messType = (pkt->data[0]) & 0xF0; if (messType == 0xF0) { if (pkt->data[0] == 0xF0) /* F0 = system exclusive. Assume 3 */ { if (pkt->data[1] != /* (for sysex: len = pkt->len) */ gSINGE_OMS->recSysexID) /* Ignore other manufacturer-ID's. */ goto rhDone; } else /* OMS receiveMIDI() other F..-messages not implemented yet. */ goto rhDone; /* SKIP these messages for now. */ } else { /* Correct OMS packet length bug. */ if ((messType == 0xC0) || /* C0 = program change. */ (messType == 0xD0)) /* D0 = channel pressure. */ len = 2; else /* F0 = system exclusive. Assume 3 */ len = 3; /* bytes for all other messages. */ } if ((!gSINGE_OMS->recOmni) && /* Selected or OMNI? */ (gSINGE_OMS->recChan != (pkt->data[0] & 0x0F))) goto rhDone; filled = (long)(oms->recBuffIn - oms->recBuffOut); if (filled < 0) /* Calculate room left in buffer. */ empty = -filled; else empty = oms->recBuffSize - filled; len2 = (long)len + 2L; /*------------------------------------ DOES THIS FIT IN CIRCULAR BUFFER? */ if (len2 >= empty) /* >= instead of > because equal pointers */ { /* means buffer is empty, not full. */ gSINGE_OMS->recOverflow = 1; /* Signal overfow. */ goto rhDone; } wr = oms->recBuffIn; /* Don't increment SHARED variable yet! */ /*------------------------------------ DOES THIS FIT NEAR END OF BUFFER? */ if ((wr + len2) > oms->recBuffWrap) /* This does not fit at the end anymore.*/ { /* Excact fit is ok. */ *wr++ = (unsigned char)0; /* Write 0-length-MSB.*/ if (wr != oms->recBuffWrap) *wr = (unsigned char)0; /* Write 0-length-LSB.*/ wr = oms->recBuffStart; /* WRAP. */ } /* (not word aligned!) */ *wr++ = (unsigned char)(len >> 8); /* Write actual (15bit?) length. */ *wr++ = (unsigned char)(len & 255); /* First MSB then LSB. */ for (i=0; idata[i]; /* Never wrap DURING a message. */ if (wr == oms->recBuffWrap) wr = oms->recBuffStart; oms->recBuffIn = wr; /* Update SHARED variable afterwards. */ rhDone: SetA5(olda5); } /*--------------------------------------------------------------------*/ /* Within a real time environment call this routine regularly to poll */ /* for newly arrived MIDI data. It will call the funtions you provide */ /* for elsewhere. It resets an overflow status after emptying buffer. */ long receiveMIDI(CMP_MIDI_IO* mio) { long processed = 0L; /* Negative return value on errors. */ unsigned int len; unsigned char *rd, *start, status, data0, channel; int bend; MD_SPECIFIC_OMS* oms; if (!mio) /* Assumes INTEGRETY of input buffer! */ return(-1L); /* See 'macMIDI_OMS.c' that writes there on the Mac.*/ oms = (MD_SPECIFIC_OMS*)(mio->specific); if (!oms) /* For the sake of 'elegance' we do not use that */ { /* global to read from, we use our argument. */ sprintf(mio->rep255, "Missing OMS specific data in receiveMIDI().\n"); return(-2L); } rd = oms->recBuffOut; while (rd != oms->recBuffIn) /* Until most recently is received. DANGEROUS because */ { /* oms->recBuffIn may increase during this while! */ if (rd >= oms->recBuffWrap - 2L) goto rdWrap; len = ((unsigned int)(*rd++)) << 8; /* Read 16 bit length.(sure adress is not too high) */ len |= (unsigned int)(*rd++); if (len == 0) { rdWrap: rd = oms->recBuffStart; len = ((unsigned int)(*rd++)) << 8; /* Read 16 bit length.(sure adress is not too high) */ len |= (unsigned int)(*rd++); } status = (*rd) & 0xF0; if (status < 0x80) { sprintf(mio->rep255,"Statusbyte (%d) < 128 in OMS receiveMIDI().\n", *rd); return(-3L); /* Unexpected status: <128 ! */ } if (*rd == 0xF0) { recMidiSystemExclusive(rd, len); /* ACTUALLY ALSO REACTS TO OTHER REAL TIME DATA. */ rd += (unsigned long)len; } else if (status == 0xF0) { sprintf(mio->rep255, "OMS receiveMIDI() real-time F..-messages not implemented yet.\n"); return(-4L); } else { start = rd; /* Remember.*/ channel = (*rd++) & 0x0F; data0 = *rd++; switch(status) /* ASSUME NO RUNNING STATUS HERE ANYMORE: */ { /* Note-on with vel=0 will NEVER occur! */ case 0x80: recMidiNoteOff (channel, data0, *rd++); break; case 0x90: if (*rd) recMidiNoteOn (channel, data0, *rd++); else recMidiNoteOff (channel, data0, *rd++); break; case 0xA0: recMidiPolyPressure (channel, data0, *rd++); break; case 0xB0: recMidiController (channel, data0, *rd++); break; case 0xC0: recMidiProgramChange (channel, data0); break; case 0xD0: recMidiChannelPressure(channel, data0); break; case 0xE0: bend = (int)data0 + (((int)(*rd++))<< 7); /* LSB,MSB. */ recMidiPitchBend (channel, bend); break; } if (rd != (start + (unsigned long)len)) { sprintf(mio->rep255, "OMS receiveMIDI() length error.\n"); return(-5L); /* Length error! */ } } processed++; /* Count number of processed messages. */ if (rd >= oms->recBuffWrap) /* > for safety */ rd = oms->recBuffStart; oms->recBuffOut = rd; /* Immediately make room for the writing-routine. */ } /* Emptied the buffer. */ mio->recOverflow = 0; /* Forget this state. */ return processed; }