home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Carousel Volume 2 #1
/
carousel.iso
/
mactosh
/
utilfil
/
midimerg.sit
/
MidiMerger.c
< prev
next >
Wrap
Text File
|
1988-12-08
|
9KB
|
356 lines
/* M i d i M e r g e r
Nick Rothwell, December '88.
*/
#include "MIDI.h" /* The low-level driving routines. */
#include "MidiMerger.h" /* MidiMerge's own typedefs/prototypes. */
typedef unsigned short COUNT;
#define INFINITY 0xFFFF /* We measure MIDI messages with an
unsigned short, so don't exceed 64K
of system exclusive. */
#define SOX 0xF0 /* Start of exclusive. */
#define EOX 0xF7 /* End of exclusive. */
#define TOP_BIT 0x80
#define REALTIME 0xF8 /* Bits determining real-time. */
#define ACTIVE_SENSE 0xFE /* Active-sensing real-time msg. */
/* midiError *MUST* be provided by the client application - it can be null
if you want to live dangerously... message is a C ('\0' terminated)
string. The state of the MidiMerge library is undefined after it has
needed to call midiError. Perhaps I need a reset command... */
extern midiError(char *message);
/* Local/library prototypes. */
static COUNT messageSize(BYTE status);
static void putByte(PORT port, BYTE byte);
static BYTE mustReadByte(PORT port);
static swallowRealtime(PORT input, PORT output, BYTE *byte);
static BYTE channeliseByte(BYTE byte, int channel);
static echoMessage(PORT input, PORT output, BYTE firstByte);
static assert(char *message, Boolean cond);
extern printf(char *template, ...);
static diagnostic(char *template, ...);
static BYTE inputStatus[2] = {0, 0},
outputStatus[2] = {0, 0};
/* Keep tracks of the last status bytes for both ports. To
do merging, we keep track of the assumed status bytes
on input and output. */
Boolean channelling[2] = {FALSE, FALSE};
/* Are we channelising the input? */
int mockChannel[2]; /* If so, the mock channel. */
assert(message, cond)
char *message;
Boolean cond;
{
if (!cond) midiError(message);
}
diagnostic(template, a, b, c, d, e, f)
char *template;
{
printf(template, a, b, c, d, e, f);
}
/* diagnostics: uncomment the first #define if you want them. */
/* #define DIAGNOSTIC(args) diagnostic args; */
#define DIAGNOSTIC(args) ; /*Just the semicolon. */
/* messageSize: how many data bytes to a message with this status? */
COUNT messageSize(status)
BYTE status;
{
switch (status&0xF0) /* Discard lowest nybble (it's the channel,
except for the system messages). */
{
case 0x80: /* NOTE OFF. */
case 0x90: /* NOTE ON. */
case 0xA0: /* POLYPHONIC AFTERTOUCH. */
case 0xB0: /* CONTROL CHANGE. */
return(2);
case 0xC0: /* PROGRAM CHANGE. */
case 0xD0: /* CHANNEL AFTERTOUCH. */
return(1);
case 0xE0: /* PITCH-WHEEL. */
return(2);
case 0xF0: /* SYSTEM MESSAGE... */
switch (status)
{
/* Common: */
case 0xF0: /* START OF EXCLUSIVE. */
return(INFINITY);
case 0xF1: /* MIDI TIME CODE 1/4 FRAME. */
return(2);
case 0xF2: /* SONG POSITION POINTER. */
return(2);
case 0xF3: /* SONG SELECT. */
return(1);
case 0xF4: /* undefined. */
case 0xF5: /* undefined. */
midiError("Unexpected status byte: 0xF4/0xF5");
case 0xF6: /* TUNE REQUEST. */
case 0xF7: /* END OF EXCLUSIVE. */
return(0);
/* Realtime: */
case 0xF8: /* TIMING CLOCK. */
case 0xF9: /* undefined. */
case 0xFA: /* START. */
case 0xFB: /* CONTINUE. */
case 0xFC: /* STOP. */
case 0xFD: /* undefined. */
case 0xFE: /* ACTIVE SENSING. */
case 0xFF: /* SYSTEM RESET. */
midiError("Unexpected real-time byte.");
}
}
midiError("messageSize: couldn't switch");
}
void channelise(port, channel)
PORT port;
int channel;
{
channelling[port] = TRUE;
mockChannel[port] = channel;
}
void noChannelise(port)
PORT port;
{
channelling[port] = FALSE;
}
void putByte(port, byte)
PORT port;
BYTE byte;
{
switch (port)
{
case MODEM: txMidiA(byte); break;
case PRINTER: txMidiB(byte);
}
}
BYTE mustReadByte(port)
PORT port;
{
long input;
do
{
input = ((port == MODEM) ? rxMidiA() : rxMidiB());
}
while (input == 0L);
return(input&0xFF);
}
/* swallowRealtime: passed a byte by reference. If the byte is a
System real-time byte, echo it out and busy-wait for a real one, and
assign the byte with this. swallowRealtime is fine when half way
through processing a message, but DON'T call it when waiting for a
message, as then an arriving real-time byte will have you busy-wait
until the next interesting input. */
static swallowRealtime(input, output, byte)
PORT input, output;
BYTE *byte;
{
while (((*byte)&REALTIME) == REALTIME)
{
if (*byte != ACTIVE_SENSE) putByte(output, *byte);
/* Don't echo active sensing bytes. */
*byte = mustReadByte(input);
}
}
BYTE channeliseByte(byte, channel)
BYTE byte;
int channel;
{
int topBits = byte&0xF0;
if (topBits == 0xF0) /* Don't do the system messages. */
return(byte);
else
return(topBits|(channel-1));
}
/* echoMessage: we've just seen "firstByte" on the input port, so we
swallow an entire MIDI message. "firstByte" may be a status byte, or
we may have to assume running status. */
echoMessage(input, output, firstByte)
PORT input, output;
BYTE firstByte;
{
int dataBytesIn;
COUNT len, i;
BYTE nextByte;
DIAGNOSTIC(("echoMessageA: %x\n", firstByte))
if (firstByte&TOP_BIT) /* Status byte? */
{
/*I think it's safe to channelise the status byte first, and then
use the new one for input running status. If the external device
changes channel, it will re-send status byte, but I'll just see
it as running status. */
if (channelling[input])
firstByte = channeliseByte(firstByte, mockChannel[input]);
inputStatus[input] = firstByte;
dataBytesIn = 0; /* Haven't read any data bytes yet.
(there may not be any!) */
}
else
dataBytesIn = 1; /* We have the first data byte. */
if (inputStatus[input] != outputStatus[output])
/* Input status byte differs from the
running status byte on the output. */
{
putByte(output, inputStatus[input]);
outputStatus[output] = inputStatus[input];
DIAGNOSTIC(("Set output status %x\n", outputStatus[output]))
}
if (dataBytesIn == 1) putByte(output, firstByte);
/* Echo out the first data byte, if
we've read it. */
len = messageSize(inputStatus[input]);
DIAGNOSTIC(("Message size(%x)=%d\n", inputStatus[input], len))
for (i = dataBytesIn; i < len; i++)
{ /* Read and echo the outstanding message, but be prepared for
a status byte before the loop exits - we do System Exclusives
by assuming infinite length, and dropping out when we hit
the EOX. */
nextByte = mustReadByte(input);
swallowRealtime(input, output, &nextByte);
if (nextByte & TOP_BIT) /* Yup, here's a status byte. It should
only be an EOX, and we should only be
processing an SOX. */
{
assert("Unexpected status byte",
(nextByte == EOX) && (inputStatus[input] == SOX)
);
putByte(output, EOX);
outputStatus[output] = EOX;
return;
}
else
putByte(output, nextByte);
}
}
/* Idle: this *MUSTN'T* be called if the Mac application is half way
through outputting a message. It can't, I hope, since I only provide
routines for outputting entire messages. */
void idleMidi(input, output)
PORT input, output;
{
long inWord;
BYTE inRealtime;
Boolean going = TRUE;
while (going)
{
/*Assumption: we NEVER leave external MIDI messages half-done. */
inWord = ((input == MODEM) ? rxMidiA() : rxMidiB());
if (inWord != 0L) /* Something from external source. */
{
if ((inRealtime = (inWord&REALTIME)) == REALTIME)
{ /* Real-time: just echo and return. */
DIAGNOSTIC(("Real-time idle.\n"))
if (inRealtime != ACTIVE_SENSE)
putByte(output, inRealtime);
going = FALSE;
}
else
echoMessage(input, output, inWord&0xFF);
} /* Swallow and echo the entire message,
loop round to see if there's more. */
else
going = FALSE; /* If there's nothing, return. */
}
}
/* transmitMidi: takes a message (sequence of bytes), the first of which *MUST*
be a status byte. It interprets this to calculate the message length, and
outputs the message atomically.
System exclusives must have the terminating EOX, which is also sent
out. */
void transmitMidi(port, message)
PORT port;
BYTE *message;
{
BYTE status = *message, b;
COUNT len, i;
assert("sendMidiA: expecting status byte", (status & TOP_BIT) != 0);
if (outputStatus[port] != status)
{
putByte(port, status);
outputStatus[port] = status;
}
len = messageSize(status);
for (i = 0; i < len; i++)
{
b = message[i+1];
if (b & TOP_BIT) /* Status byte? */
{
assert("transmitMidi: unexpected status byte",
(status == SOX) && (b == EOX)
);
putByte(port, EOX);
outputStatus[port] = EOX;
return;
}
else
putByte(port, b);
}
}
void startMidi(port)
PORT port;
{
switch (port)
{
case MODEM: initSccA(); break;
case PRINTER: initSccB();
}
}
void stopMidi(port)
PORT port;
{
switch (port)
{
case MODEM: resetSccA(); break;
case PRINTER: resetSccB();
}
}