/* 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 <pascal.h> /* 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 <toolutils.h>
#include <AppleEvents.h>
#include <EPPC.h>
#include <Gestalt.h>
#include <PPCToolbox.h> 
#include <Processes.h>
#include <Sound.h>


#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<<BUFFOVFL)) 
        gprintf(ERROR, "Midi Buffer Overflow Error\n");
    if (midi_error_flags & (1<<MIDIMGRERR)) 
        gprintf(ERROR, "Midi Manager Error\n");
    if (midi_error_flags & (1<<SYSEXOVFL)) 
        gprintf(ERROR, "Midi Sysex Overflow Error\n");
}


/**************** PATCHING CODE ***************/

/*
        MIDIArp Time, Input, and Output Port 
        Info Record Resource ID's.
*/

/* Get previously saved port connections (port info records) */
/* from application's 'port' resource. */
void
PatchPorts(void)
{
        MIDIPortInfoHdl PortInfoH;      /* Handle to port info record. */
        MIDIPortInfoPtr PortInfoP;      /* Pointer to port info record. */
        short                   i, TheErr;
        
        patch_flag = cl_switch("patch");
                
                /* SET UP TIME PORT CONNECTIONS. */
        if (patch_flag)
                PortInfoH = (MIDIPortInfoHdl) GetResource(portResType, timePortResInfoID);
        if (!patch_flag || PortInfoH == NULL) {
                MIDIIDListHdl clients, ports;
                OSErr err;
                
                gprintf(TRANS, "Connecting to MIDI IN and OUT\n");
#ifdef MIDIMGR_VERBOSE
                clients = MIDIGetClients();
                gprintf(TRANS, "clients = %lx\n", clients);
                HLock((Handle) clients);
                
                for (i = 0; i < (*clients)->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; i<PortInfoP->numConnects; 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; i<PortInfoP->numConnects; 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; i<PortInfoP->numConnects; 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 */