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.