home *** CD-ROM | disk | FTP | other *** search
- /* 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();
- }
- }
-
-