ftp.nice.ch/pub/next/unix/music/cm.sd.tar.gz#/midi/mcl-midi.c

This is mcl-midi.c in view mode; [Download] [Up]

/* 
 * File: mcl-midi.c
 *
 * These c functions interface with the Apple Midi Manager and are part of
 * the Macintosh Common Lisp v2.0px port of Common Music (CM), written by 
 * Rick Taube (hkt@zkm.de).
 *
 * Bug reports, suggestions, etc. are welcome. Please address electronic
 * mail to <tkunze@mvax.kgw.tu-berlin.de>.
 *
 * Note: it is strongly recommended that you use MPW v3.1 or higher, if you
 *       need to recompile this file.
 *       You _will_ need MPW v3.2 or higher to run this code under System 7
 *       or higher.
 *
 * CAUTION: ALWAYS MAKE SURE THE HARDCODED OFFSETS IN midicsetup ARE IN FULL
 *          COMPLIANCE WITH THE ACTUALLY GENERATED CODE AFTER COMPILING!
 *
 * Changes:
 *   15-6-93 tk  - although i couldn't test it on such a machine up to now,
 *                 the new cache flushing mechanism should now work well on
 *                 the MC68040 line of machines (or on a machine with cache
 *                 card).
 *               - invisible input queues get flushed completely before the
 *                 code returns to normal (non-flushing) operation.
 *               - myInvisibleReadHook now invalidates time stamps when in
 *                 flushing mode.
 *   12-6-93 tk  - fixed bug in myInvisibleReadHook that didn't recognize
 *                 note-off messages properly.
 */

#include <midi.h>
#include <memory.h>


/* These two functions are not in the interfaces: */

void FlushCodeCache (void) = 0xA0BD;

    /* MPW C 3.2 makes register-based inline calls very efficient. However,
     * MPW C 3.1 and earlier, and THINK Cª should declare the function as
     * ÒpascalÓ and use the same inline constants as the Pascal interface:
     * pascal OSErr FlushCodeCacheRange (void *address, unsigned long count)
     *   = {0x225F, 0x205F, 0x7009, 0xA198, 0x3E80};
     */

#pragma parameter __D0 FlushCodeCacheRange(__A0,__A1)
OSErr FlushCodeCacheRange (void *address, unsigned long count) =
    {0x7009, 0xA198};

/*
 * MCL's ff-interface does not provide an A5 pointer to functions that are not
 * called directly from lisp, therefore all static data has been 'hardcoded'
 * right into the code itself as automatic variable declarations of the form:
 *
 *    volatile type name=hex_data;
 *
 * The function midicsetup then stores all relevant data into these fields,
 * thus guaranteeing that they hold their right values at interrupt time.
 */

#define channel_tag   1
#define system_tag    2
#define TRUE          1
#define FALSE         0
#define hwParamErr -502
#define noErr         0

/*--------------------------------------------------------------------------*
 *  Functions related to reading midi data                                  *
 *--------------------------------------------------------------------------*
 *                                                                          *
 *  Summary:                                                                *
 *    myReadHook is a standard readhook which calls Lisp's midi-read-hook.  *
 *    myInvisibleReadHook gets all current packets from all buffers which   *
 *    are allocated for the invisible input ports and which are completely  *
 *    maintained by the MidiManager.                                        *
 *    setReadHook gets called everytime a fresh invisible pair of output    *
 *    and input ports is created to set the input port's ReadHook to        *
 *    myInvisibleReadHook.                                                  *
 *                                                                          *
 *--------------------------------------------------------------------------*/

/*
 * myReadHook may not call lisp from within an interrupt. Therefore, it is 
 * set up (by setting the input_port's offset time to midiGetNothing) to 
 * respond only to the midiPoll(input_port,midiGetCurrent) call issued by 
 * midireadevent. midireadevent, in turn, is called directly from lisp and 
 * thus will never execute at interrupt time.
 * Since lots of MidiManager error packets are waiting after our various
 * output buffer overflows, return midiMorePacket to quickly flush all 
 * unknown message types. Also, according to 'Midi Support in Common Music',
 * all pending messages are read in response to one single midireadevent call
 * (see cm/doc/midi.rtf), therefore return midiMorePacket for known messages,
 * too.
 */

extern pascal short myReadHook(MIDIPacketPtr packet, long myRefCon)
{
    volatile void (*lisp_call_read) (int,int)=0x12345678;
    int message, time;

    if (((packet->flags & midiContMask)==midiNoCont) &&
        ((packet->flags & midiTypeMask)==midiMsgType) &&
        ((packet->flags & midiTimeStampMask)==midiTimeStampValid)) {
        message = (((channel_tag << 26) & 0xc000000) |
                   ((packet->len-6 << 24) & 0x03000000) |
                   ((packet->data[0] << 16) & 0xff0000) |
                   ((packet->data[1] <<  8) & 0xff00) |
                   (packet->data[2] & 0xff));
        time=(int)packet->tStamp;    
        (*(ProcPtr)lisp_call_read) (message, time) ; 
        return midiMorePacket;
    }
    return midiMorePacket;
}


/* 
 * The ReadHook for all invisible ports.
 * This ReadHook is called whenever a future midi output stored in the
 * invisible input buffer becomes current. The hook simply passes the current
 * message to the real output port. However, when an output queue flushing 
 * is performed (by setting the offsetTime of _all_ invisible input ports to
 * midiGetEverything, causing all pending messages to be dumped), the ReadHook
 * checks the message type and passes only note-offs, ignoring all other 
 * message types. Thus, multiple notes hanging on the same channel and key
 * will be turned off properly.
 */
 
extern pascal short myInvisibleReadHook(MIDIPacketPtr packet, long myRefCon)
{
    volatile short flushing=0x1234;                    /* the flushing flag */
    
    if (flushing) { 
        if ((packet->data[0] & 0xf0)==0x80) {            /* if note-off ... */
		    packet->flags|=0x80;              /* invalidate the time stamp! */
            if (MIDIWritePacket(myRefCon, packet) == 0)          /* success */
                return 1;                                /* MIDIMorePacket 1*/
            return 0;                                    /* MIDIKeepPacket 0*/
        }
		else return 1;                                   /* MIDIMorePacket 1*/
    }
    else if (MIDIWritePacket(myRefCon, packet) == 0)             /* success */
        return 1;                                        /* MIDIMorePacket 1*/
    else return 0;                                       /* MIDIKeepPacket 0*/
}


void setReadHook(long refNum)
{
    MIDISetReadHook((short)refNum,(ProcPtr)&myInvisibleReadHook);
}


/* 
 * if MidiPoll wouldn't be a Pascal PROCEDURE and at least return an
 * OSErr value, we would be able to test for empty buffers, but ... ($^#@*!!)
 */

short midireadevent ()
{
    volatile short inport=0x1234;
    
    MIDIPoll(inport,midiGetCurrent);
    return 0;
}



/*--------------------------------------------------------------------------*
 *  invisible ports database and handling functions                         *
 *--------------------------------------------------------------------------*
 *                                                                          *
 *  Summary:                                                                *
 *    All invisible ports are held in a global inv_ports structure. Port    *
 *    pairs are added, when %mm-make-aux-buffers calls add_port_pair. Port  *
 *    pairs are removed at interrupt level by myTimeProc (but not yet       *
 *    deallocated). %mm-write-message needs a function which performs a     *
 *    rotating permutation on the port pairs; this function is called       *
 *    cycle_port_pairs.                                                     *
 *    The Remove_Not field is examined by myTimeProc to prevent any removal *
 *    of port pairs while they are possibly accessed by a lisp function     *
 *    that writes to them (%mm-write-message and %mm-all-notes-off).        *
 *                                                                          *
 *--------------------------------------------------------------------------*/

struct p_pair {
   short outport;
   short inport;
   };

struct inv_ports {        /* is initialized by midicsetup */
   short Remove_Not;      /* used as a flag (boolean) */
   short last_index;      /* zero-based: index of last element in array */
   struct p_pair port_pair[];
   } *myports;
   
struct rem_ports {
   short nextfree;        /* points to the first free place in port_pair[] */
   struct p_pair port_pair[];
   } *oldports;

void add_port_pair(long outport, long inport)
{
    short i,j;
    struct p_pair temp={(short)outport,(short)inport};
    
        /* move every pair one place to the back... */
    for (i=myports->last_index,j=i+1;i>=0;i--,j--)
        myports->port_pair[j]=myports->port_pair[i];
        /* ...and put the packed long [outport,inport] at position 0 */
    myports->port_pair[0]=temp;
    (myports->last_index)++;
}


void cycle_port_pairs(long position) /* zero-based index of the new head */
{
    struct p_pair temp;
    short nr_of_pairs=myports->last_index+1;
    short pop_pos, push_pos, tmp;
    
        /* save the old beginning for later appending */
    temp=myports->port_pair[0];
        /* cycle through the positions until position 0 is reached again */
    for (pop_pos=(short)position, push_pos=0;
         pop_pos>0;
	 push_pos=pop_pos,tmp=pop_pos+(short)position,
	     pop_pos=(tmp < nr_of_pairs ? tmp : tmp-nr_of_pairs))
        myports->port_pair[push_pos]=myports->port_pair[pop_pos];
	/* restore the temp pair to its new place */
    myports->port_pair[push_pos]=temp;
}


/*--------------------------------------------------------------------------*
 *  see-whether-buffers-expired sniffer functions                           *
 *--------------------------------------------------------------------------*
 *                                                                          *
 *  Summary:                                                                *
 *    Once CommonMusic has signed into the MidiManager, a periodic callback *
 *    is scheduled with a time interval of wakeUpPeriod.                    *
 *    The callback procedure myTimeProc checks whether a buffer should be   *
 *    considered expired. It does this by looping through all outports and  *
 *    examining their refCon field, which holds the time stamp of the last  *
 *    message written to that port. If a time stamp is more than            *
 *    bufIdleTimeout ms old, the whole port_pair gets moved to the r_ports  *
 *    for later removal at non-interrupt time :-| and deleted from the list *
 *    of available port_pairs.                                              *
 *    However, if the Remove_Not field of myports is not zero, one of the   *
 *    midi output writing routines is possibly accessing one of the ports,  *
 *    so we simply wait until we're called again in this case.              *
 *                                                                          *
 *--------------------------------------------------------------------------*/
 
extern pascal void myTimeProc(long curTime, long myRefCon)
{
    volatile long bufIdleTimeout=0x12345678;
    volatile struct inv_ports *i_ports=0x12345678;
    volatile short main_invoutport_id=0x1234;
    volatile struct rem_ports *r_ports=0x12345678;
    short idx,j,l_i,tmp;
    long threshold=curTime-bufIdleTimeout;
    struct p_pair temp={(short)0,(short)0};
        
        /* if there are any auxiliary buffers ... */
    if ((l_i=i_ports->last_index)>0) {
        if (i_ports->Remove_Not) {
	    return;                /* do nothing, wait for the next WakeUp */
	    }
	else {
	        /* look through every port_pair for older RefCon's */
	    for (idx=0;idx<=l_i;idx++) {
	        tmp=i_ports->port_pair[idx].outport;
	        if ((tmp!=main_invoutport_id) && 
		    (MIDIGetRefCon(tmp)<=threshold)) {
	                /* don't release them at interrupt level! instead, 
			 * transfer their entry to r_ports */
		    r_ports->port_pair[r_ports->nextfree]=i_ports->port_pair[idx];
		    r_ports->nextfree++;
		        /* shift the remaining port_pairs forward one place */
		    for (j=idx+1;j<=l_i;j++) {
		        i_ports->port_pair[j-1]=i_ports->port_pair[j];
			}
			/* clear the last one */
	            i_ports->port_pair[l_i]=temp;
		        /* update the last_index field */
		    i_ports->last_index=(--l_i);
	            }
		    }
	    }
	}
}

/*
 * Sort of CLRQ.L with all field offsets of *oldports hardcoded. This function
 * is called at non-interrupt level from nearly all ff-midi's %mm-siblings.
 * I know this may lead to undeallocated empty buffers hanging around and 
 * defragmenting the mac heap until midi is closed again, but i don't want to 
 * mess with mcl's event dispatch or *eventhook* mechanism to queue special 
 * 'ports-to-remove' events. One might think about queueing an specialized 
 * AppleEvent some time in the future, but this means setting up a AppleEvent 
 * record at interrupt level and writing an AppleEvent handler...
 * Anyway, since this is a rare problem and since empty ports are removed
 * automatically the next time one of those functions are called, i decided to
 * leave it as it is for now.
 */

void remove_ports()
{
    long *baseaddress=(char *)oldports+2;
    long *firstfree=baseaddress+oldports->nextfree;  /* increment as (long *) */
    
    for (;baseaddress<firstfree;baseaddress++) {
        MIDIRemovePort((short)((*baseaddress)>>16));
	MIDIRemovePort((short)(*baseaddress));
	*baseaddress=0L;
	}
	/* reset nextfree to 0 */
    *(short *)oldports=(short)0;
}


/*--------------------------------------------------------------------------*
 *  Functions related with output buffer flushing                           *
 *--------------------------------------------------------------------------*
 *                                                                          *
 *  Summary:                                                                *
 *    flushAllBuffers sets the internal switch of myInvisible ReadHook to   *
 *    its flushing state and schedules an one-time callback to switch it    *
 *    back later again. After millisecs time, which it gets from the time   *
 *    port, myCleanUpProc terminates flushing.                              *
 *    Again, since the MidiManager provides no way to determine whether     *
 *    an input port's buffer is empty, this approach may lead to problems   *
 *    if there were _huge_ amounts of future packets awaiting delivery,     *
 *    but could not be processed within the given time.                     *
 *    To prevent those unprocessed notes from dropping out of the midi      *
 *    device at their scheduled time again (long after midi output has been *
 *    flushed), myCleanUpProc also removes any unprocessed messages from    *
 *    the (main) invisible input port's queue by calling MIDIFlush.         *
 *                                                                          *
 *    Since a self-modifying code approach is used for myInvisibleReadHook, *
 *    FlushCodeCacheRange or FlushCodeCache are called from both,           *
 *    flushAllBuffers and myCleanUpProc. However, FlushCodeCacheRange may   *
 *    not be implemented for some older versions of system software that    *
 *    are not MC68040-aware. In this case, FlushCodeCacheRange returns      *
 *    hwParamErr (Ð502) and it is necessary to flush the entire cache       *
 *    instead, using FlushCodeCache. If FlushCodeCacheRange succeeds it     *
 *    returns noErr (0).                                                    *
 *                                                                          *
 *--------------------------------------------------------------------------*/

/*
 * After some time, this cleanup function simply terminates flushing, so that
 * we can continue with normal playing again.
 */
 
extern pascal void myCleanUpProc(long curTime, long myRefCon)
{
    volatile short *flushing_ptr=0x12345678;
    volatile struct inv_ports *i_ports=0x12345678;
    short i, inv_inport;
        
        /* flush all invisible inports and reset their offset times to 
		 * midiGetCurrent */
	for (i=0;i<=i_ports->last_index;i++) {
	    inv_inport=i_ports->port_pair[i].inport;
	    MIDIFlush(inv_inport);
	    MIDISetOffsetTime(inv_inport, midiGetCurrent);
		}
		/* reset the flushing flag */
    *flushing_ptr=(short)FALSE;
		/* flush the instruction cache, but 
		 * try to flush only the code word at flushing_ptr */
	if (hwParamErr==FlushCodeCacheRange((void *)flushing_ptr,2L))
	    FlushCodeCache();
}


/* 
 * refNum is the refNum of our time port. We schedule an additional callback
 * in millisecs ms (our time port runs in ms) to do any necessary cleanup.
 */
 
void flushAllBuffers()    
{
    volatile short time_port=0x1234;
    volatile long flush_timeout=0x12345678;
    volatile short *flushing_ptr=0x12345678;
    volatile struct inv_ports *i_ports=0x12345678;
    short i;
    
    *flushing_ptr=(short)TRUE;
		/* try to flush only the code word at flushing_ptr */
	if (hwParamErr==FlushCodeCacheRange((void *)flushing_ptr,2L))
	    FlushCodeCache();
    for (i=0;i<=i_ports->last_index;i++)
        MIDISetOffsetTime(i_ports->port_pair[i].inport, midiGetEverything);
    MIDIWakeUp(time_port, (MIDIGetCurTime(time_port)+flush_timeout), 
               0, (ProcPtr)&myCleanUpProc);
}
 

/*--------------------------------------------------------------------------*
 *  Initialization function                                                 *
 *--------------------------------------------------------------------------*
 *                                                                          *
 *  Summary:                                                                *
 *     midicsetup does all initialization tasks.                            *
 *     There is no need to check for the availability of either the         *
 *     _HwPriv or _CacheFlush traps that get called by FlushCodeCacheRange  *
 *     or FlushCodeCache, respectively, since                               *
 *       o  MCL 2.0 requires System 6.0.4, and                              *
 *       o  _HwPriv is implemented in the Macintosh IIx ROMs and later, as  *
 *          well as System 6.0.3 and later, and                             *
 *       o  _HwPriv expands to a call to _CacheFlush.                       *
 *                                                                          *
 *--------------------------------------------------------------------------*/
 
struct inv_ports *midicsetup(timeport, inputport, outputport,
                             invinputport, invoutputport,
                             lisp_c_read,
                             wakeupperiod, bufidletimeout, flushtimeout, ptrsize)
    int timeport, inputport, outputport, invinputport, invoutputport; 
    void (*lisp_c_read) (int msg, int tim);
    long wakeupperiod, bufidletimeout, flushtimeout, ptrsize;
{

/*
 * Since mcl's ff-interface preloads all its functions into a locked block,
 * the function address is a pointer to an JMP $XXXXXXXX instruction. The 
 * long value after the JMP instruction holds the actual address.
 * So we first typecast &somefunction to be a char pointer, increment it by
 * 2 ('chars': the JMP instruction takes two bytes), and then typecast it back
 * to be a pointer to a long and return its contents (the address we wanted).
 */

#define real_Address(x) *(long *)(((char *)(x))+2)

    long *readHookPtr=real_Address(&myReadHook);
    long *InvreadHookPtr=real_Address(&myInvisibleReadHook);
    long *readEventPtr=real_Address(&midireadevent);
    long *timeProcPtr=real_Address(&myTimeProc);
 /* long *scheduleCBPtr=real_Address(&scheduleCallBack); */
    long *cleanUpPtr=real_Address(&myCleanUpProc);
    long *flushBufsPtr=real_Address(&flushAllBuffers);
    
    struct p_pair temp={(short)invoutputport,(short)invinputport};
    short psize=ptrsize-4;
    
/***
 *** CAUTION: Always make sure this and the following offsets are in full
 ***          compliance with the actually generated code after compiling!
 ***          You may use MPW's dumpobj tool to compare the offsets.
 ***/

    /* the pointer to the flushing flag */
    short *flushingptr=(short *)((char *)InvreadHookPtr+0x12);
    
    /* allocate a block to hold both, an inv_ports and a rem_ports structure */
    myports=(struct inv_ports *)NewPtrClear(ptrsize);
    if (!myports)
        return 0;
    /* rem_ports offset to inv_ports */
    oldports=(struct rem_ports *)((long)myports+((ptrsize+2)>>1));
    /* initialize the structures */
    myports->Remove_Not=(short)0;
    myports->last_index=(short)0;
    myports->port_pair[0]=temp;
    
    /* Initialization of the hardcoded variables.
     * They are located at their (real) function addresses + specific offsets:
     */

    *(long *)((char *)readHookPtr   + 0xe)  = (long)lisp_c_read;
    *flushingptr                            = (short)FALSE;
    *(short *)((char *)readEventPtr + 0x6)  = (short)inputport;
    *(long *)((char *)timeProcPtr   + 0xa)  = (long)bufidletimeout;
    *(long *)((char *)timeProcPtr   + 0x12) = myports;
    *(short *)((char *)timeProcPtr  + 0x1a) = (short)invoutputport;
    *(long *)((char *)timeProcPtr   + 0x20) = oldports;
    *(long *)((char *)cleanUpPtr    + 0xa)  = (long)flushingptr;
    *(long *)((char *)cleanUpPtr    + 0x12) = myports;
    *(short *)((char *)flushBufsPtr + 0x8)  = (short)timeport;
    *(long *)((char *)flushBufsPtr  + 0xe)  = (long)flushtimeout;
    *(long *)((char *)flushBufsPtr  + 0x16) = (long)flushingptr;
    *(long *)((char *)flushBufsPtr  + 0x1e) = myports;
    
    MIDISetReadHook((short)inputport,(ProcPtr)&myReadHook);
    MIDISetReadHook((short)invinputport,(ProcPtr)&myInvisibleReadHook);

        /* schedule a periodic callback to release empty buffers */
    MIDIWakeUp((short)timeport,0,wakeupperiod,(ProcPtr)&myTimeProc);
    
    return myports;
}





/* ------------------------------------------------------------------------*/
/* ------------------------------------------------------------------------*/


/* unused - the lisp version is just as fast */

/*
int midiwritemessage(message, qtime)
    int message; int qtime;
{
    MIDIPacket packet;
    int byte=16;
    int size=(message & 0x03000000) >> 24;
    int i;

    packet.flags=0;
    packet.len=(unsigned)9;
    packet.tStamp=(long)qtime;
    message=message & 0xffffff;
    for (i=0; i<size; i++) {
    packet.data[i]=(char)((message >> byte) & 0xff);    
    byte -= 8;
    }
    MIDIWritePacket(output_port, (MIDIPacketPtr)&packet);
    return 0;
}
*/


/*
 * EOF
 */

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.