/* midimgr.c -- this file contains interface code to support use of Apple Midi Manager */ /* * This code is based on code supplied with the Apple Midi Manager. * Copyright 1991, Carnegie Mellon University */ /* BUGS: * If exclusive() is called to turn exclusive messages on or off DURING the * receipt of an exclusive message, incoming data will be garbled. The correct * handling would be to record when receipt of an exclusive message is in * progress, then properly remove any partial message when exclusive is turned * off, and ignore any remaining message part when exclusive is turned on. * The present code does neither. */ #include "cext.h" #undef round #ifdef THINK_C #include /* for ThinkC 7 */ #endif #include "stdio.h" #include "userio.h" #include "MIDI.h" #include "midifns.h" #include "midibuff.h" #include "midierr.h" #include "midimgr.h" #include "midicode.h" #include "cmdline.h" /* Needed for KillEverybody */ #include #include #include #include #include #include #include #define CMTclientID 'CMT ' /* note the following are in alphabetical order for Patcher display */ #define timePortID 'Atim' #define inputPortID 'Bin ' #define outputPortID 'Cout' #define noClient ' ' #define noTimeBaseRefNum 0 #define noReadHook 0L #define zeroTime 0L #define timePortBuffSize 0L #define inputPortBuffSize 2048 #define outputPortBuffSize 0L #define refCon0 0L pascal short CMTreader(MIDIPacket *ThePacketPtr, long TheRefCon); /* "patch" switch from command line. This switch is cached in patch_flag and tells whether to look in the resource fork for a patch, or just hook up to midi in and out. If the resource fork is used, the patch will be saved upon exit. */ private boolean patch_flag; extern boolean ctrlFilter; extern boolean exclFilter; extern boolean realFilter; private midi_read_lock = false; /* used to stop input during data structure manipulation */ private void set_error(int bit); #ifndef NYQUIST void PatchPorts(void); void SavePatch(OSType PortID, short PortInfoResID, char *PortInfoResName); #endif /* exported: */ public short InputRefNum; /* Input port reference number. */ public short OutputRefNum; /* Output port reference number. */ public short TimeRefNum; /* Time base port reference number. */ Boolean GManualPatch; /* True if not launched by a PatchBay Config. File. */ /**************************************************************************** * * variables shared with other modules * ****************************************************************************/ /* midi input buffer */ long buff[BUFF_SIZE/4]; /* data buffer, declared long to get 32-bit alignment */ int buffhead = 0; /* buffer head and tail pointers */ int bufftail = 0; /* user supplied system exclusive buffer */ byte *xbuff = NULL; /* address of the user-supplied buffer */ public long xbufmask; /* mask for circular buffer address calculation */ long xbufhead = 0; /* buffer head and tail pointers */ long xbuftail = 0; boolean xbuf_flush = true; /* says to flush remainder of sysex message */ #ifdef SYSEXDEBUG int sysexcount = 0; /* for debugging */ int sysexdone = 0; int sysexheadcount = 0; byte sysexfirst = 0; int sysexsysex = 0; #endif /* midi_flush -- empty out buffers */ /**/ void midi_flush() { midi_read_lock = true; buffhead = 0; bufftail = 0; xbufhead = 0; xbuftail = 0; xbuf_flush = true; /* in case sysex continuation messages are still coming */ midi_read_lock = false; } /* Nyquist only uses CMT for Midi and Adagio file IO */ #ifndef NYQUIST /* Get String representation of MIDI Mgr Version Num.*/ /* See Mac Tech Note #189 for details. */ char *StdMacVerNumToStr(long VerNum, char *VerStr) { char *RetVal; char MajVer, MinVer, VerStage, VerRev, BugFixVer = 0; if (VerNum == 0) { RetVal = NULL; } else { MajVer = (VerNum & 0xFF000000) >> 24; MinVer = (VerNum & 0x00FF0000) >> 16; VerStage = (VerNum & 0x0000FF00) >> 8; VerRev = (VerNum & 0x000000FF) >> 0; BugFixVer = MinVer & 0x0F; switch (VerStage) { case 0x20: VerStage = 'd'; break; case 0x40: VerStage = 'a'; break; case 0x60: VerStage = 'b'; break; case 0x80: VerStage = ' '; break; default: VerStage = '?'; break; } if (BugFixVer == 0) { sprintf(VerStr,"%X.%X%c%X", MajVer, MinVer>>4, VerStage, VerRev); } else { sprintf(VerStr,"%X.%X.%X%c%X", MajVer, MinVer >> 4, MinVer & 0x0F, VerStage, VerRev); } RetVal = VerStr; } return(RetVal); } /* C2PStrCpy -- Convert a C String (from Cstr) into a Pascal string */ /* * NOTE: this is not the same code as shipped with midi manager example */ char *C2PStrCpy(char *Cstr, Str255 Pstr) { char *c = Cstr; char *p = ((char *) Pstr) + 1; while (*c) *p++ = *c++; *Pstr = c - Cstr; return( (char *) Pstr ); } /* This checks to see if THINK C is running under System 7, and ONLY WORKS UNDER SYSTEM 7! Don't use unless you check! */ boolean ThinkCRunning(void) { ProcessSerialNumber processSN; OSErr myErr; ProcessInfoRec infoRec; processSN.lowLongOfPSN = kNoProcess; processSN.highLongOfPSN = kNoProcess; do { myErr = GetNextProcess(&processSN); infoRec.processInfoLength = sizeof(ProcessInfoRec); infoRec.processName = 0L; infoRec.processAppSpec = 0L; myErr = GetProcessInformation(&processSN, &infoRec); if (!myErr) { if (infoRec.processSignature == 'KAHL') { return(true); } } } while (myErr == noErr); return(false); } /* This kills off all the other running processes... ONLY WORKS UNDER SYSTEM 7! Don't use unless you check! */ void KillEverybody(void) { ProcessSerialNumber myProc, processSN; ProcessSerialNumber finderPSN; ProcessInfoRec infoRec; Str31 processName; FSSpec procSpec; OSErr myErr = noErr; OSErr otherError; AppleEvent theEvent; AEDesc theAddress; Boolean ourFlag, notFinder; Boolean finderFound = false; GetCurrentProcess(&myProc); /* Preset the PSN to no PSN, see IM VI, the Process Manager */ processSN.lowLongOfPSN = kNoProcess; processSN.highLongOfPSN = kNoProcess; finderPSN.lowLongOfPSN = 0UL; /* brk: was nil */ finderPSN.highLongOfPSN = 0UL; /* brk: was nil */ do { myErr = GetNextProcess(&processSN); /* See if it's us first */ notFinder = true; SameProcess(&myProc, &processSN, &ourFlag); infoRec.processInfoLength = sizeof(ProcessInfoRec); infoRec.processName = (StringPtr) &processName; infoRec.processAppSpec = &procSpec; GetProcessInformation(&processSN, &infoRec); if (!ourFlag && !finderFound) { /* see if it's the Finder, we have to kill the finder LAST */ /* or else non-sys 7 apps won't get killed */ /* since the Finder must be there to convert the AppleEvent to Puppet Strings */ /* if the app is not APpleEvent aware */ /* Also, FileShare HAS to be killed before the Finder */ /* or your life will be unpleasant */ if (infoRec.processSignature == 'MACS' && infoRec.processType == 'FNDR') { /* save this number for later */ finderPSN = processSN; notFinder = false; finderFound = true; } else { notFinder = true; } } if (!myErr && !ourFlag && notFinder) { otherError = AECreateDesc(typeProcessSerialNumber, (Ptr)&processSN, sizeof(processSN), &theAddress); if (!otherError) otherError = AECreateAppleEvent(kCoreEventClass, kAEQuitApplication, &theAddress, kAutoGenerateReturnID, kAnyTransactionID, &theEvent); if (!otherError) AEDisposeDesc(&theAddress); /* Again, the Finder will convert the AppleEvent to puppetstrings if */ /* the application is a System 6 or non-AE aware app. This ONLY */ /* happens for the 4 required (oapp,odoc,pdoc, and quit) AppleEvents */ /* and ONLY if you use the PSN for the address */ if (!otherError) AESend(&theEvent, 0L, kAENoReply + kAEAlwaysInteract + kAECanSwitchLayer, kAENormalPriority, kAEDefaultTimeout, 0L, 0L); AEDisposeDesc(&theEvent); } } while (!myErr); /* Now, if the finder was running, it's safe to kill it */ if (finderPSN.lowLongOfPSN || finderPSN.highLongOfPSN) { otherError = AECreateDesc(typeProcessSerialNumber, (Ptr)&finderPSN, sizeof(processSN), &theAddress); if (!otherError) otherError = AECreateAppleEvent(kCoreEventClass, kAEQuitApplication, &theAddress, kAutoGenerateReturnID, kAnyTransactionID, &theEvent); if (!otherError) AEDisposeDesc(&theAddress); if (!otherError) AESend(&theEvent, 0L, kAENoReply + kAEAlwaysInteract + kAECanSwitchLayer, kAENormalPriority, kAEDefaultTimeout, 0L, 0L); AEDisposeDesc(&theEvent); } } /* Sign into the MIDI Manager. */ /* Set up time, input, and output ports. */ /* Start our time base clock. */ void setup_midimgr(void) { MIDIPortParams Init; /* MIDI Mgr Init data structure */ Handle TheIconHndl; OSErr TheErr; long MIDIMgrVerNum; /* MIDI Manager Ver (Std Mac Ver #) */ Str255 name = "\pCMU MIDI Toolkit"; char MIDIMgrVerStr[256]; /* MIDI Manager Ver (Std Mac Ver # String) */ long vers; EventRecord theEvent; Gestalt(gestaltSystemVersion, &vers); vers = (vers >> 8) & 0xf; /* shift result over and mask out major version number */ if ((vers >= 7) && (!cl_switch("keep")) && (!ThinkCRunning())) { gprintf(TRANS,"Killing other processes...\n"); KillEverybody(); for (vers=0; vers<100; ++vers) { while (WaitNextEvent(everyEvent, &theEvent, 0L, 0L)) ; } } /* Make sure MIDIMgr is installed and save version num. */ MIDIMgrVerNum = SndDispVersion(midiToolNum); if (MIDIMgrVerNum == 0) { gprintf(ERROR, "The MIDI Manager is not installed! Exiting...\n"); EXIT(1); } else { StdMacVerNumToStr(MIDIMgrVerNum, MIDIMgrVerStr); gprintf(TRANS,"MIDI Manager Version %s\n", MIDIMgrVerStr); } /* Sign in to the MIDI Manager. */ TheIconHndl = GetResource('ICN#', 1); TheErr = MIDISignIn(CMTclientID, 0L, TheIconHndl, name); if (TheErr) { gprintf(ERROR, "Trouble signing into MIDI Manager! Aborting..."); EXIT(1); } /* Assume not a Patchbay configuration. */ GManualPatch = true; /* Add time port. */ Init.portID = timePortID; Init.portType = midiPortTypeTime; Init.timeBase = noTimeBaseRefNum; Init.readHook = noReadHook; Init.initClock.syncType = midiInternalSync; Init.initClock.curTime = zeroTime; Init.initClock.format = midiFormatMSec; Init.refCon = SetCurrentA5(); C2PStrCpy("TimeBase", Init.name); TheErr = MIDIAddPort(CMTclientID, timePortBuffSize, &TimeRefNum, &Init); /* Has a PatchBay connection been resolved? */ if (TheErr == midiVConnectMade) { GManualPatch = false; } else if (TheErr == memFullErr) { gprintf(ERROR, "Not enough room in heap zone to add time port! Aborting..."); MIDISignOut(CMTclientID); EXIT(1); } /* Add an input port. */ Init.portID = inputPortID; Init.portType = midiPortTypeInput; Init.timeBase = TimeRefNum; Init.offsetTime = midiGetCurrent; Init.readHook = NewMIDIReadHookProc(CMTreader); Init.refCon = SetCurrentA5(); C2PStrCpy("InputPort", Init.name); TheErr = MIDIAddPort(CMTclientID, inputPortBuffSize, &InputRefNum, &Init); /* Has a PatchBay connection been resolved? */ if (TheErr == midiVConnectMade) { GManualPatch = false; } else if (TheErr == memFullErr) { gprintf(ERROR, "Not enough room in heap zone to add input port! Aborting..."); MIDISignOut(CMTclientID); EXIT(1); } /* Add an output port. */ Init.portID = outputPortID; Init.portType = midiPortTypeOutput; Init.timeBase = TimeRefNum; Init.offsetTime = midiGetCurrent; Init.readHook = NULL; Init.refCon = refCon0; C2PStrCpy("OutputPort", Init.name); TheErr = MIDIAddPort(CMTclientID, outputPortBuffSize, &OutputRefNum, &Init); /* Has a PatchBay connection been resolved? */ if (TheErr == midiVConnectMade) { GManualPatch = false; } else if (TheErr == memFullErr) { printf("Not enough room in heap zone to add output port! Aborting..."); MIDISignOut(CMTclientID); EXIT(1); } if (GManualPatch) { PatchPorts(); /* connect ports as they were */ } /* to clean this up (later) call finish_midimgr() */ cu_register((cu_fn_type) finish_midimgr, (cu_parm_type) finish_midimgr); /* Start our Clock. */ MIDIStartTime(TimeRefNum); } /* The Read Hook Function. */ /* 1st 4 bytes of sysex message get saved here and enqueued later */ char save_sysex_head[4]; int save_sysex_head_x = 0; void sysex_insert(unsigned char data) { if (save_sysex_head_x < 4) { save_sysex_head[save_sysex_head_x++] = data; } xbuff[xbuftail++] = data; xbuftail &= xbufmask; if (xbuftail == xbufhead) { set_error(SYSEXOVFL); } if (data == MIDI_EOX) { /* we're done with the message */ *((long *) (((byte *) buff) + bufftail)) = *((long *)save_sysex_head); bufftail = (bufftail + 4) & BUFF_MASK; if (bufftail == buffhead) { set_error(BUFFOVFL); } } } /* Read all incomming MIDI data. */ pascal short CMTreader(MIDIPacket *ThePacketPtr, long TheRefCon) { /* Set up our A5 world. */ long SysA5 = SetA5(TheRefCon); short RetVal = midiMorePacket, i, j; unsigned char *mm_data = ThePacketPtr->data; register byte data1 = mm_data[1]; if (midi_read_lock) { /* Don't want to read packet now, get it later */ /* DOES THIS REALLY WORK? WHAT WILL CAUSE AN INTERRUPT * TO OCCUR LATER? THIS IS ONLY USED BY midi_flush, IS * BASED ON THE MidiArp CODE FROM APPLE, AND IS UNTESTED - RBD */ RetVal = midiKeepPacket; goto alldone; } /* see if Packet is an error message */ if (((ThePacketPtr->flags & midiTypeMask) == midiMgrType) && *((short *) (&(ThePacketPtr->data))) < midiMaxErr) { set_error(MIDIMGRERR); goto alldone; } /* filter out control changes */ if (ctrlFilter) { register int hibits = *mm_data & 0xF0; if (hibits == 0xD0 || /* Chan Pressure */ hibits == 0xE0 || /* Pitch Bend */ hibits == 0xA0 || /* Poly Pressure */ ((hibits == 0xB0) && /* Control change (don't count switches) */ ((data1 < 64) || (data1 > 121)))) { /* CONTROL MESSAGE HAS BEEN FILTERED */ goto alldone; } } else if (realFilter) { register int hibits = *mm_data & 0xF0; if (hibits >= 0xF8) goto alldone; } /* if not a continuation, copy the data into cmt_data */ /* The logic to detect a non-continued * packet or a first packet is: "flags bit 1 is clear". */ if ((((ThePacketPtr->flags & midiContMask) == midiNoCont)) && (*mm_data != MIDI_SYSEX)) { register byte *cmt_data = ((byte *) buff) + bufftail; *((long *) cmt_data) = *((long *) mm_data); bufftail = (bufftail + 4) & BUFF_MASK; if (bufftail == buffhead) { /* filled buffer faster than client emptied it */ set_error(BUFFOVFL); } } /* see if we have a sysex message to copy to buffer */ if (xbuff && !exclFilter && ((ThePacketPtr->flags & midiContMask) || *mm_data == MIDI_SYSEX)) { int i; register byte *x_data = xbuff + xbuftail; /* iterate over data in message */ /* NOTE: in the previous implementation, I thought Sysex messages were * always starting at the beginning of the buffer, but that didn't work. * This implementation assumes nothing -- it is slower because of additional * testing and parsing inside the loop, but seems to work. */ for (i = ThePacketPtr->len - 6; i > 0; i--) { if (xbuf_flush) { /* we're searching for beginning of message */ if (*mm_data == MIDI_SYSEX) { xbuf_flush = false; sysex_insert(MIDI_SYSEX); } } else { /* we're scanning to the end of the message */ if (*mm_data == MIDI_SYSEX) { /* found it, insert proper EOX */ sysex_insert(MIDI_EOX); sysex_insert(MIDI_SYSEX); } else if (*mm_data == MIDI_EOX) { /* found it */ sysex_insert(MIDI_EOX); xbuf_flush = true; } else sysex_insert(*mm_data); } mm_data++; } } alldone: /* Restore the systems A5 world. */ SetA5(SysA5); return(RetVal); } /* Sign out from the MIDI Manager. */ void finish_midimgr(void) { if (GManualPatch && patch_flag) { SavePatch(timePortID, timePortResInfoID, "timePortInfo"); SavePatch(inputPortID, inputPortResInfoID, "inputPortInfo"); SavePatch(outputPortID, outputPortResInfoID, "outputPortInfo"); } MIDISignOut(CMTclientID); } /* Alert user to Resource Manager Error. */ void ReportResError(char *Msg) { OSErr TheErr; char Buf[256]; if ( (TheErr = ResError()) != noErr) { gprintf(ERROR, "ResError %d: %s...Aborting.", TheErr, Msg); EXIT(1); } else { /* gprintf(ERROR, "%s OK\n", Msg); */ } } /**************************************************************************** * error handling * Effect: * various error conditions are flagged by setting bits in * the global midi_error_flags. it is up to the client to clear this * word when necessary. ****************************************************************************/ private void set_error(int bit) { midi_error_flags |= (1 << bit); } void midi_show_errors() { if (midi_error_flags & (1<numIDs; i++) { OSType id = (*clients)->list[i]; gprintf(TRANS, "%d: %c%c%c%c\n", i, (char) (id>>24), (char) ((id >> 16) & 0xFF), (char) ((id >> 8) & 0xFF), (char) (id & 0xFF)); } ports = MIDIGetPorts('amdr'); HLock((Handle) ports); for (i = 0; i < (*ports)->numIDs; i++) { OSType id = (*ports)->list[i]; gprintf(TRANS, "%d: %c%c%c%c\n", i, (char) (id>>24), (char) ((id >> 16) & 0xFF), (char) ((id >> 8) & 0xFF), (char) (id & 0xFF)); } HUnlock((Handle) ports); HUnlock((Handle) clients); #endif /* the work starts here */ err = MIDIConnectData('CMT ', 'Cout', 'amdr', 'Aout'); /* gprintf(TRANS, "Connected CMT.Cout to amdr.Aout: %d\n", err); */ err = MIDIConnectData('amdr', 'Ain ', 'CMT ', 'Bin '); /* gprintf(TRANS, "Connected amdr.Ain to CMT.Bin: %d\n", err); */ return; } HLock((Handle) PortInfoH); PortInfoP = *PortInfoH; if (GetHandleSize((Handle) PortInfoH) != 0) { /* Were we supposed to be sync'd to another client? */ if (PortInfoP->timeBase.clientID != noClient) { /* Yes, so make that client our time base. */ TheErr = MIDIConnectTime( PortInfoP->timeBase.clientID, PortInfoP->timeBase.portID, CMTclientID, timePortID ); #ifdef IGNORE /* Is the client still signed in? */ if (TheErr != midiVConnectErr) { /* Yes, so set our sync mode to external. */ MIDISetSync(ArpGlobals.TimeRefNum, midiExternalSync); } #endif } /* Were we somebody else's time base? */ for (i=0; inumConnects; i++) { MIDIConnectTime(CMTclientID, timePortID, PortInfoP->cList[i].clientID, PortInfoP->cList[i].portID); } } HUnlock((Handle) PortInfoH); ReleaseResource((Handle) PortInfoH); ReportResError("PatchPorts/ReleaseResource()"); /* SET UP INPUT PORT CONNECTIONS. */ PortInfoH = (MIDIPortInfoHdl) GetResource(portResType, inputPortResInfoID); if (PortInfoH == NULL) { ReportResError("PatchPorts/GetResource()"); } HLock((Handle) PortInfoH); PortInfoP = *PortInfoH; if (GetHandleSize((Handle) PortInfoH) != 0) { /* Were we connected to anyone? */ for (i=0; inumConnects; i++) { MIDIConnectData(CMTclientID, inputPortID, PortInfoP->cList[i].clientID, PortInfoP->cList[i].portID); } } HUnlock((Handle) PortInfoH); ReleaseResource((Handle) PortInfoH); ReportResError("PatchPorts/GetResource()"); /* SET UP OUTPUT PORT CONNECTIONS. */ PortInfoH = (MIDIPortInfoHdl) GetResource(portResType, outputPortResInfoID); if (PortInfoH == NULL) { ReportResError("PatchPorts/GetResource()"); } HLock((Handle) PortInfoH); PortInfoP = *PortInfoH; if (GetHandleSize((Handle) PortInfoH) != 0) { /* Were we connected to anyone? */ for (i=0; inumConnects; i++) { MIDIConnectData(CMTclientID, outputPortID, PortInfoP->cList[i].clientID, PortInfoP->cList[i].portID); } } HUnlock((Handle) PortInfoH); ReleaseResource((Handle) PortInfoH); ReportResError("PatchPorts/ReleaseResource()"); } /* Save current port connections (port info records) */ /* to application's 'port' resource. */ void SavePatch(OSType PortID, short PortInfoResID, char *PortInfoResName) { Handle PortResH; /* Handle to ptch resource. */ CursHandle WatchCurs; WatchCurs = GetCursor(watchCursor); HLock((Handle) WatchCurs); SetCursor(*WatchCurs); HUnlock((Handle) WatchCurs); /* Remove existing port info resource. */ PortResH = GetResource(portResType, PortInfoResID); /* gprintf(TRANS, "PortResH: %lx, *PortResH: %lx\n", PortResH, *PortResH); */ if (PortResH) { ReportResError("SavePatch/GetResource()"); RmveResource(PortResH); ReportResError("SavePatch/RmveResource()"); DisposHandle(PortResH); UpdateResFile(CurResFile()); ReportResError("SavePatch/UpdateResFile()"); } /* Get new configurateion. */ PortResH = (Handle) MIDIGetPortInfo(CMTclientID, PortID); /* Save new configurateion. */ CtoPstr(PortInfoResName); AddResource(PortResH, portResType, PortInfoResID, (ConstStr255Param) PortInfoResName); PtoCstr((unsigned char *) PortInfoResName); ReportResError("SavePatch/AddResource()"); WriteResource(PortResH); ReportResError("SavePatch/WriteResource()"); UpdateResFile(CurResFile()); ReportResError("SavePatch/UpdateResFile()"); ReleaseResource(PortResH); ReportResError("SavePatch/ReleaseResource()"); InitCursor(); } #endif /* NYQUIST */