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

This is next-3-0-midi.c in view mode; [Download] [Up]

/* interface to NeXT 3.0 midi driver.
 * if using Franz, compile by: cc -DEXCL next-3-0-midi.c -c -O -o next-midi.o
 *
 * This version queues up bytes locally to avoid blocking.
 */

#ifndef EXCL
	#ifndef  AKCL
		#error "missing compilation flag: need AKCL or EXCL !"
	#endif
#endif

#import <mach.h>
#import <stdio.h>
#import <stdlib.h>
#import <mach_error.h>
#import <signal.h>
#import <servers/netname.h>
#import <libc.h>
#import <appkit/nextstd.h>
#import <mididriver/midi_driver.h>
#import <mididriver/midi_spec.h>
#import <cthreads.h>

static int foo;  /* For debugging */

static port_t driverPort;     /* Port for driver on particular host. */
static port_t ownerPort;      /* Port that represents ownership */
static port_t dataPort;       /* Port for incoming data. */
static port_t queuePort = PORT_NULL; /* Port for output queue notification messages */
static port_t exceptionPort;  /* Port for timing exceptions */
static port_t alarmPort;      /* To get periodic messages. */
static int maxQueueSize;      /* Maximum output queue size */
static int halfQueueSize;      /* Maximum output queue size/2 */
static int unit;              /* Serial port to send to */
static port_set_name_t ports; /* Port set to listen for messages from driver */

static int quantaSize=1000;
static int quantaTime=0;

/* Thread Globals */

int Listening = 0;
int Writing = 0;
mutex_t     queueLock = NULL; 
mutex_t     listeningLock; 
mutex_t     schedulerLock; 

/* Handler Declarations */

static void myDataReply(port_t replyPort, short unit,
			MIDIRawEvent *eventbuf, unsigned int count); 
static void myAlarmReply(port_t replyPort, int requestedTime, int actualTime); 
static void myExceptionReply(port_t replyPort, int exception);
static void myQueueReply(port_t replyPort, short unit);

/* MIDI Messages */

typedef struct _MIDIMessage {
    unsigned long time;
    unsigned long message;
    struct _MIDIMessage *next;
} MIDIMessage;

static MIDIMessage *firstMessage = NULL;
static MIDIMessage *lastMessage = NULL;
static int lastTime = -1; 
static int Scheduling;

#define SCHEDULER_ADVANCE 50 

void startScheduler()
{
    Scheduling = 1;
}

void stopScheduler()
{
    mutex_lock(schedulerLock);
    Scheduling = 0;
    mutex_unlock(schedulerLock);
    firstMessage = NULL;
    lastMessage = NULL;
}

 /* forward decls */

static int manageOverflowQueue(void);
int allNotesOff(void);

int midiopen(int port)		/* port=0 for A, 1 for B */
{
    kern_return_t r;
    static int firstTime = 1;
    int synchUnit;		/* Serial port to listen for time code */

    if (port==0) unit=MIDI_PORT_A_UNIT;
    else if (port==1) unit=MIDI_PORT_B_UNIT;
         else return KERN_FAILURE;

    r = netname_look_up(name_server_port, "","mididriver", &driverPort);
    if (r != KERN_SUCCESS) return r;
    r = port_allocate(task_self(), &ownerPort);
    if (r != KERN_SUCCESS) return 0;
    r = MIDIBecomeOwner(driverPort,ownerPort);
    if (r != KERN_SUCCESS) return r;
    r = MIDIClaimUnit(driverPort, ownerPort,unit);
    if (r != KERN_SUCCESS) return r;
    r = MIDISetClockMode(driverPort, ownerPort, synchUnit,
    			 MIDI_CLOCK_MODE_INTERNAL);
    if (r != KERN_SUCCESS) return r;
    r = MIDISetClockQuantum(driverPort, ownerPort, quantaSize);
    if (r != KERN_SUCCESS) return r;
    r = MIDISetSystemIgnores(driverPort, ownerPort, unit,
    			     MIDI_IGNORE_REAL_TIME);
    if (r != KERN_SUCCESS) return r;

    /* create the various ports */
    if (queuePort == PORT_NULL) {
	r = port_allocate(task_self(), &queuePort);
	if (r != KERN_SUCCESS) return r;
    }
    r = port_allocate(task_self(), &exceptionPort);
    if (r != KERN_SUCCESS) return r;
    r = port_allocate(task_self(), &dataPort);
    if (r != KERN_SUCCESS) return r;
    r = port_allocate(task_self(), &alarmPort);
    if (r != KERN_SUCCESS) return r;

    /* create the port set and add the ports */

    r = MIDIRequestExceptions(driverPort, ownerPort, exceptionPort);
    if (r != KERN_SUCCESS) return r;
    r = MIDIGetAvailableQueueSize(driverPort, ownerPort, unit, &maxQueueSize);
    if (r != KERN_SUCCESS) return r;
    halfQueueSize = maxQueueSize / 2;
    r = MIDISetClockTime(driverPort, ownerPort, quantaTime);
    if (r != KERN_SUCCESS) return r;
    r = MIDIStartClock(driverPort, ownerPort);
    if (r != KERN_SUCCESS) return r;

    /* DAJ: Moved this to here.  Note that you only have to do it once, not
     * every invocation of midireadmessages().
     */
    r = MIDIRequestData(driverPort, ownerPort, unit, dataPort);
    if (r != KERN_SUCCESS) return r;

    if (!queueLock) {
      listeningLock = mutex_alloc(); 
      schedulerLock = mutex_alloc();
      queueLock = mutex_alloc();     
    }
    Listening = 0;
    if (firstTime)
      cthread_detach(cthread_fork((cthread_fn_t)manageOverflowQueue, (any_t)0)); 
    firstTime = 0;
/*    startScheduler(); */
    return KERN_SUCCESS;
}

int midisettime(int time); /* Forward decl */

int midiclose()
{
    kern_return_t r;
 
    if (!driverPort || !ownerPort) return KERN_SUCCESS;
/*     r=allNotesOff(); */
    midisettime(0); /* Frees up all structure that hasn't gone out. */
    r = MIDIReleaseOwnership(driverPort,ownerPort);
    if (r != KERN_SUCCESS) return r;
    r = port_deallocate(task_self(), exceptionPort);
    if (r != KERN_SUCCESS) return r;
    r = port_deallocate(task_self(), dataPort);
    if (r != KERN_SUCCESS) return r;
    r = port_deallocate(task_self(), alarmPort);
    if (r != KERN_SUCCESS) return r;
    quantaTime=0;
    quantaSize=1000;
    if (r != KERN_SUCCESS) return r;
    return KERN_SUCCESS;
}


int midistarttimer()
{
    return MIDIStartClock(driverPort, ownerPort);
}


int midistoptimer()
{
    return MIDIStopClock(driverPort, ownerPort);
}


int midisetquantasize(int usec)
{
    quantaSize=usec;
    return MIDISetClockQuantum(driverPort, ownerPort, usec);
}

typedef struct _queuedBytes {MIDIRawEvent theEvent; 
			    struct _queuedBytes *next;
			    } queuedBytes, *queuedBytesPtr;

static volatile queuedBytesPtr head = NULL;
static volatile queuedBytesPtr tail = NULL;

int midisettime(int time)
{
    queuedBytes *tmp;
    quantaTime=time;
    mutex_lock(queueLock);
    while (head) {
      tmp = head;
      head = head->next;
      free(tmp);
    }
    tail = NULL;
    mutex_unlock(queueLock);
    MIDIClearQueue(driverPort,ownerPort,unit);
    return MIDISetClockTime(driverPort, ownerPort, time);
}


int midigettime(int *time)
{
 kern_return_t r;
 
    r = MIDIGetClockTime(driverPort, ownerPort, &quantaTime);
    if (r != KERN_SUCCESS) return r;
    time[0]=quantaTime;
    time[1]=quantaSize;
    return KERN_SUCCESS;
}

static int pushBytesOnQueue(MIDIRawEvent *events,int size)
{
    int i;
    mutex_lock(queueLock);
    for (i = 0; i<size; i++) {
	if (!head) {
	    tail = head = malloc(sizeof(queuedBytes));
	    tail->theEvent = *events++;
	    tail->next = NULL;
	}
	else {
	    tail->next = malloc(sizeof(queuedBytes));
	    tail = tail->next;
	    tail->theEvent = *events++;
	    tail->next = NULL;
	}
    }
    mutex_unlock(queueLock);
    return 0;
}

static void myQueueReply(port_t replyPort,short unit)
    /* This gets invoked when the queue has enough room for more data. */
{
    kern_return_t r;
    int i,thisCount,count;
    MIDIRawEvent events[MIDI_MAX_EVENT];
    MIDIRawEvent *p;
    queuedBytes *tmp;
    int shouldReenqueue;

    /* Warning:  This depends on the fact that halfQueueSize is greater than
     * MIDI_MAX_EVENT.  If NeXT ever changes that, this code will fail.
     */

    mutex_lock(queueLock);
    for (count = 0; (head != NULL) && (count + MIDI_MAX_EVENT < halfQueueSize); 
	 count += thisCount) {
	p = events;
	for (i = 0; i<MIDI_MAX_EVENT && head; i++) {    
	    *p++ = head->theEvent;
	    tmp = head;
	    head = head->next;
	    free(tmp);
	}
	if (!head) 
	  tail = NULL;
	mutex_unlock(queueLock); /* Watching out for deadlock */
	thisCount = p-events;
	if (thisCount)
	  MIDISendData(driverPort, ownerPort, unit, events, p-events);
	mutex_lock(queueLock);
    }
    shouldReenqueue = (head != NULL);
    mutex_unlock(queueLock);
    if (shouldReenqueue)
      MIDIRequestQueueNotification(driverPort, ownerPort, 
				   unit, queuePort, halfQueueSize);
}

int midiwritemessage(int message, int qtime)
{
    kern_return_t r = KERN_SUCCESS;
    MIDIRawEvent events[16];
    int byte=16;
    int size=(message & 0x03000000) >> 24;
    int i;
    message=message & 0xffffff;
    for (i=0; i<size; i++) {
	events[i].time=qtime;
	events[i].byte=(char)((message >> byte) & 0xff);    
	byte -= 8;
    }
    if (head)  /* No room in driver */
      return pushBytesOnQueue(events,size);
    r = MIDISendData(driverPort, ownerPort, unit, events, size);
    if (r == MIDI_ERROR_QUEUE_FULL) {
	/* Request notification when at least half the queue is 
	 * available */
		pushBytesOnQueue(events,size);
		r = MIDIRequestQueueNotification(driverPort, ownerPort, 
					 unit, queuePort, 
					 halfQueueSize);
    } 
    return r;
}


int midireadmessages()
{
    kern_return_t r;
    char msgBuf[MIDI_MAX_MSG_SIZE];
    msg_header_t *msg = (msg_header_t *)msgBuf;
    MIDIReplyFunctions funcs = {0, 0, 0, 0};
    int keepGoing = TRUE;

    funcs.dataReply = (MIDIDataReplyFunction)myDataReply;

    while (keepGoing) { 
	msg->msg_local_port = dataPort; 
	msg->msg_size = MIDI_MAX_MSG_SIZE;
	r = msg_receive(msg, (RCV_TIMEOUT | RCV_INTERRUPT), 0); /* Changed to 0 by daj */
	switch (r) {
	    case RCV_SUCCESS:
	        r = MIDIHandleReply(msg, &funcs); /* Simplifed by DAJ */
		break;
	    case RCV_TIMED_OUT:
		r = KERN_SUCCESS;
	    default:
		keepGoing = FALSE;
		break;
	}
    } 
    return r;
}

static int manageOverflowQueue(void)
{
    kern_return_t r;
    char msgBuf[MIDI_MAX_MSG_SIZE];
    msg_header_t *msg = (msg_header_t *)msgBuf;
    MIDIReplyFunctions funcs = {0, 0, 0, 0};
    funcs.queueReply = (MIDIQueueReplyFunction)myQueueReply;
    for (;;) {
	MIDIAwaitReply(queuePort,&funcs,MIDI_NO_TIMEOUT);
    } 
    return r;
}

int midiflushrecv()
{
    return KERN_SUCCESS;
}

int midiflushxmit()
{
    return KERN_SUCCESS;
}

#ifdef EXCL
#define MIDI_INPUT_HOOK	0
#endif

#define DataTag		0x0
#define ChannelTag	0x4000000
#define SystemTag	0x8000000
#define MetaTag		0xc000000


static void myDataReply(port_t replyPort, short unit,
			MIDIRawEvent *eventbuf, unsigned int count) 
{
    static unsigned char status = 0;
    static int length = 0;
    static int datastart = 0;
    static int bytenum = 0;
    static int datalsh = 0;
    static unsigned typeid = 0;
    static unsigned message = 0;
    MIDIRawEvent *e;
    char data;
#ifdef EXCL
    long lisp_call(int index, unsigned msg, unsigned tim); 
#endif    
#ifdef AKCL
	void MIDI_INPUT_HOOK(int msg, int tim);
#endif
    for (e = eventbuf; count--; e++) {
	data = e->byte;
	if (data & MIDI_STATUSBIT) {
	    status = data;
	    typeid = (MIDI_TYPE_SYSTEM(status)) ? SystemTag : ChannelTag;
	    if (MIDI_TYPE_1BYTE(status)) {
	        length = 1;
	        datastart = 0;
	    }
	    else if (MIDI_TYPE_2BYTE(status)) {
		length = 2;
		datastart = 0;
	    }
	    else {
		length = 3;
		datastart = 8;
	    }
	    datalsh = datastart;
	    bytenum = 1;
	    message = 0;	
	}
	else {
	    message |= ((data << datalsh) & (0xff << datalsh));
	    if (datalsh == 0)
		datalsh = datastart;
	    else datalsh -= 8;
	    bytenum++;
	}
	if (bytenum == length) {
	    message |= (typeid | ((length << 24) & 0x03000000) |
				 ((status << 16) & 0xff0000));
#ifdef EXCL
	    lisp_call(MIDI_INPUT_HOOK, message, (unsigned)e->time);
#endif
#ifdef AKCL
		MIDI_INPUT_HOOK((int)message,(int)e->time);
#endif
	    bytenum = 1;
	    message = 0;
	    datalsh = datastart;
	}
    }
}    

static void myAlarmReply(port_t replyPort, int requestedTime, int actualTime)
{
}

static void myExceptionReply(port_t replyPort, int exception)
{
}

int midiallnotesoff(int time)
{
    int c,k,m;
    kern_return_t r;

    for (c=0;c<16;c++)
        for (k=0;k<128;k++)
        {   /* cobble up note off message */
            m=0x7800040 | ((c<<16) & 0xf0000) | ((k<<8) & 0xff00);
            r=midiwritemessage(m,time);
            if (r != KERN_SUCCESS)
                return r;
        }
    return KERN_SUCCESS;
}

int midihush (void)
{
    #define NOTEOFF_ARRAY_SIZE (128*2)          /* Use running status */
    MIDIRawEvent arr[NOTEOFF_ARRAY_SIZE]; 
    kern_return_t r;
    int chan = 0;
    int bytesToSend,i;
    MIDIRawEvent op;
    MIDIReplyFunctions funcs = {0};
    for (i=0; i<128; i++)
	{  /* Initialize array */
		arr[i*2].byte = i;   /* KeyNum */
		arr[i*2+1].byte = 0; /* Velocity */
    }
	/* Empty out ports of any old notification messages. */
    while (MIDIAwaitReply(ports,&funcs,1) == KERN_SUCCESS)
		;     
	r = MIDIClearQueue(driverPort,ownerPort,unit);
    if (r != KERN_SUCCESS) return r;
    for (chan = 0; chan < 16; chan++)
	{
		op.byte = MIDI_NOTEOFF | chan;
		r = MIDISendData(driverPort,ownerPort,unit,&op,1);
	    if (r != KERN_SUCCESS) return r;
		for (i = 0; i < NOTEOFF_ARRAY_SIZE; i += MIDI_MAX_EVENT) 
		{
		    bytesToSend = NOTEOFF_ARRAY_SIZE - i;
		    if (bytesToSend > MIDI_MAX_EVENT)
			bytesToSend = MIDI_MAX_EVENT;
		    r = MIDISendData(driverPort,ownerPort,unit,&arr[i],bytesToSend);
	        if (r != KERN_SUCCESS) return r;
  		}
	r = MIDIFlushQueue(driverPort,ownerPort,unit);
    if (r != KERN_SUCCESS) return r;
	r = MIDIRequestQueueNotification(driverPort, ownerPort,unit,queuePort,
					 ((chan==15) ? maxQueueSize : NOTEOFF_ARRAY_SIZE+1));
    if (r != KERN_SUCCESS) return r;
	r = MIDIAwaitReply(ports,&funcs,MIDI_NO_TIMEOUT);
    if (r != KERN_SUCCESS) return r;
    }
	return KERN_SUCCESS;
}

/* * * * * * * * * * * * * unused * * * * * * * * * */

void midilistener(int ignore)
{
    kern_return_t r;
    char msgBuf[MIDI_MAX_MSG_SIZE];
    msg_header_t *msg = (msg_header_t *)msgBuf;
    MIDIReplyFunctions funcs = {0, 0, 0, 0};

    while (Listening) { 
	funcs.dataReply = (MIDIDataReplyFunction)myDataReply;
	funcs.queueReply = (MIDIQueueReplyFunction)myQueueReply;
	msg->msg_local_port = ports; 
	msg->msg_size = MIDI_MAX_MSG_SIZE;
	r = msg_receive(msg, 0, 0);   /* This hangs waiting.  If you want it
					 not to, use 2nd arg of RCV_TIMEOUT - daj */
	if (r == RCV_SUCCESS) 
	  r = MIDIHandleReply(msg, &funcs);
	
    } 
    cthread_yield();
}

int midilisten()
{
    kern_return_t r;
    r = MIDIClearQueue(driverPort,ownerPort,unit);
    if (r != KERN_SUCCESS)
      return r;
    mutex_lock(listeningLock);
    Listening=1;
    mutex_unlock(listeningLock);
    cthread_detach(cthread_fork((cthread_fn_t)midilistener, (any_t)0)); 
    return KERN_SUCCESS;
}

int midistoplistening()
{
    kern_return_t r;
    mutex_lock(listeningLock);
    Listening=0;
    mutex_unlock(listeningLock);
    r = MIDIClearQueue(driverPort,ownerPort,unit);
    return r;
}

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