home *** CD-ROM | disk | FTP | other *** search
- /* CMIDI: Anodyne's interface to the MIDI Manager. Make sure this module is
- in the same segment of the project as main(), so that the interrupt handler
- here is loaded and locked (that's Playing Safe - if my reading of the
- Segment Loader docs is correct, we're OK anyway if the handlers are
- declared static) (Yup, 'cos Symantec tech. support told me so).
- This module is essentially a simple channelising echo, plus facilities
- to output arbitrary messages. We have a single input port, since those of
- us lucky enough to own two keyboards can have the MIDI Manager do the
- merging for us. Anodyne doesn't distinguish between input sources. We drive
- several outputs; Anodyne keeps track of output (and channel) for each device.
- We don't echo SysEx messages by default, but can choose to capture them
- for the application.
-
- Nick Rothwell, September 1989. (rewrite in TC4.0, November 1990.) */
-
- #include <TCL>
- #include "GLOBAL.h"
- #include "CMIDI.h"
- #include "CUtility.h"
- #include "CDashboard.h"
- #include "CDiagnostic.h"
- #include "midi.h"
- #include "chars.h" /* Need the OSLASH char for my signature! */
- #include "ICN.h"
- #include "STR.h"
-
- #define ENABLED
-
- #define BUFSIZE 2048 /* MIDI port buffer size. */
- #define MAX_MESSAGE 249 /* Why isn't this defined in MIDI.h?? */
- #define PACKET_JUNK 6 /* Number of bytes other than data. */
- #define UNLOAD_TIMEOUT 1000 /* Ticks. */
- #define POLL_TIME 100 /* Msec. polling time for output routine
- when the output buffer is empty. */
- #define BYTES_PER_MSEC 3
- /* Used to calculate the pause between
- packets. Slightly conservative figure
- (it's actually 3.125), for safety. */
- #define MAX_DIAGS 100 /* Circular buffer of diag. messages. */
- #define MAX_QUEUE 10 /* Output packet buffer. */
-
- #define APPLE_MIDI_DRIVER 'amdr'
- #define APPLE_DRIVER_IN1 'Ain '
- #define APPLE_DRIVER_IN2 'Bin '
- #define APPLE_DRIVER_OUT1 'Aout'
- #define APPLE_DRIVER_OUT2 'Bout'
- #define ANODYNE_TIME 'Zeit'
- #define ANODYNE_IN 'In '
- #define ANODYNE_OUT1 'Out1'
- #define ANODYNE_OUT2 'Out2'
-
- /* Locking/unlocking of input/echo (see below). I hope "++" and "--" are
- atomic in this C compiler... */
-
- #define LOCK (G.lockDepth++)
- #define UNLOCK (G.lockDepth--)
-
- /* A few words about System Exclusive capture and locking. The structure of
- the various critical sections is, err, critical. If we want to capture a
- sequence of system exclusive messages, without missing any or getting into
- a race condition, we have to be very careful. To do so, we temporarily lock
- out the reader, install a pointer to the capture buffer along with some
- size counting stuff, and unlock. Whenever we've captured a complete SysEx
- dump, we lock ourselves, uninstall the capture buffer, and unlock. This
- puts us back into our default state: echo everything on a certain cable
- and channel, ignore SysEx's.
- There's a small fault here: we may be half-way through dealing with
- a multi-packet system exclusive anyway, in which case the capture will
- catch the end of it. I'm quite happy to ignore this (or fix it elsewhere)
- rather than add complexity to the interrupt routines. */
-
- /* Transmission of messages. Sending a big SysEx dump as fast as we can is
- a bad thing to do. We'll either overrun the SCC or the input buffer of the
- receiving MIDI application. Even if the instrument inserts calls to Pause(),
- each individual message might be extremely long (Ensoniq VFX anyone?). One
- way out is to spin-wait between packets, but this is ugly and makes the
- application's response sluggish. A better way is to tie the output port to
- a timebase and timestamp the outgoing messages. Calls to Pause() simply
- bump up the outgoing timestamp. This is better, but we still run the risk
- of overflowing the destination's input buffer (and the SCC?). If we send
- a D-50 dump like this, it will emerge with the right delays between
- packets, but only if the 36K or so of data can sit somewhere in the meantime.
- The only solution seems to be to do local output buffering. MM packets are
- placed into a circular output buffer with timestamps (and dest. cables)
- attached. A WakeUp routine which we attach to our timebase transmits any
- messages (freeing the buffer slot) if the message timestamp is current (in
- fact, slightly old). We have to use a
- MIDIWakeUp routine to fire off messages; if it's a normal routine tied into
- the event loop, we may be away from the event loop sufficiently long for
- a lot of pending messages to pile up; they'll then all go off at once.
- The wake-up routine runs at a default polling speed when the output buffers
- are empty, and as soon as it sees any activity it outputs current messages,
- re-scheduling itself for each pending message whose time has not come.
- Once the buffer is empty again, it goes back to ticking at the polling
- rate. This is a bit of a hack - the polling rate has to be high enough to
- avoid a lot of messages piling up, but slow enough to leave the machine
- alive (Mac Plus, anyone?) - but simpler than alternative schemes involving
- rescheduling of a possibly active wake-up routine (I couldn't get that to
- work reliably, so...). */
-
- /* Stop press - the non-polled version (better since it only runs the
- WakeUp task when something goes into the buffer) works fine except under
- the THINK C debugger! Film at 11. Non-polled is better, since we start a
- WakeUp routine when something goes into the buffer (we assume this cancels
- any outstanding WakeUp), and let it expire when the buffer becomes empty.
- The debugger seems to interfere with this, so if you want the debugger,
- turn on the flag for the polled version instead. It's slightly more
- sluggish but functionally equivalent (I hope).
- My guess: the Debugger grabs time when we restart the WakeUp routine
- inside Dispatch - the result being that we cue it into the past. This
- might explain the failure on the Mac Plus as well. */
-
- /* Input locking. We lock when silencing an output cable (there are tidier
- ways, but...) The output interrupt routine also locks/unlocks when it
- sends out continuation sequences (assumed to be for one cable at once).
- Hence, the lock is a semaphore (almost) rather than a flag, just in case
- some lock/unlock sequences coincide. */
-
- /* A static record to hold the variables needed by the input interrupt
- routines. It doesn't need to be a record, but that makes things clearer.
- Even with this, we still need the A5 magic to be able to call any
- routines. */
-
- typedef struct {
- CABLE cable;
- MIDIPacket packet;
- } OutputSlotRec, *OutputSlotPtr;
-
- static struct
- {
- Boolean pollingVersion; /* TEST: polled vs. non-polled version. */
-
- int lockDepth; /* Lock out the input routine. */
-
- /*Capture information. */
-
- struct {
- Boolean active; /* Are we capturing? */
- Boolean error; /* Capture error detected? */
- Boolean finished; /* We've got a fish. */
- CInstrument *instrument; /* Owner of the capture data. */
- int howMany; /* How many SysEx's to capture? */
- long spaceTotal; /* Total capture space. */
- Byte *base; /* Base of capture area. */
- Byte *ptr; /* Destination for SysEx data. */
- long spaceLeft; /* Space left. */
- } capture;
-
- struct {
- int refNum; /* Input/timebase port refnums. */
- } input, timebase;
-
- struct {
- int refNum[NUM_CABLES]; /* Reference num for output ports. */
- RouteRec echoRoute; /* Route for echoing. */
- RouteRec xmitRoute; /* Route for SysEx (etc.) transmission. */
- } output;
-
- long dispatchTime; /* timestamp for outgoing messages. */
-
- struct { /* The output packet queue. */
- OutputSlotPtr queue;
- int produce;
- int consume;
- } outputQueue;
-
- /*We can have one outstanding error at a time: */
- struct {
- int index; /* If non-zero, it's the STR# index for an
- error string. */
- int extra; /* Extra error info. */
- } error;
-
- struct {
- struct {
- char *mesg;
- long extra1, extra2;
- } buffer[MAX_DIAGS]; /* Circular buffer of diagnostics. */
- int produce;
- int consume;
- } diags;
- } G;
-
- /* All the local, vanilla C routines may be called at interrupt time. No calls
- to other segments are wise here, nor calls to ToolBox routines. */
-
- /* We maintain a circular buffer of diagnostics which the interrupt routine
- is allowed to post to. */
-
- LOCAL void postDiagnostic(char *mesg, long extra1, long extra2)
- {
- G.diags.buffer[G.diags.produce].mesg = mesg;
- G.diags.buffer[G.diags.produce].extra1 = extra1;
- G.diags.buffer[G.diags.produce].extra2 = extra2;
- G.diags.produce = (G.diags.produce+1) % MAX_DIAGS;
- }
-
- LOCAL void postError(int mesg, int extra)
- {
- G.error.index = mesg;
- G.error.extra = extra;
- }
-
- LOCAL void dealWithSysEx(MIDIPacketPtr packet)
- {
- int len = packet->len - PACKET_JUNK;
- Byte cont = packet->flags&midiContMask;
-
- if (!G.capture.active)
- return; /* Not capturing - ignore.*/
-
- if (len <= G.capture.spaceLeft) {
- BlockMove(&packet->data[0], G.capture.ptr, len);
- G.capture.ptr += len;
- G.capture.spaceLeft -= len;
-
- #if 0
- postDiagnostic("Captured one", G.capture.howMany, G.capture.spaceLeft);
- #endif
-
- /*If this packet ends a complete SysEx, decrement the howMany count. */
- if (cont == midiNoCont || cont == midiEndCont) {
- if (--G.capture.howMany == 0) { /* End of dump. */
- G.capture.active = FALSE; /* Back to normal. We must leave
- the other capture fields for
- inspection. */
- G.capture.finished = TRUE;
- }
- }
- } else { /* Overflow! */
- postError(OVERFLOW_index, len);
- G.capture.active = FALSE;
- G.capture.error = TRUE;
- }
- }
-
- LOCAL void dealWithInput(MIDIPacketPtr inPacket)
- {
- Byte cont = inPacket->flags&midiContMask;
- Byte status, /* Status byte */
- status0; /* Status byte sans channel. */
- int refNum;
- CABLE cable;
- MIDIPacket outPacket;
- OSErr err;
-
- switch (cont) {
- case midiNoCont:
- status = inPacket->data[0];
- status0 = status&0xF0;
-
- switch (status0) {
- case 0x80: /* NOTE OFF. */
- case 0x90: /* NOTE ON. */
- case 0xA0: /* POLYPHONIC AFTERTOUCH. */
- case 0xB0: /* CONTROL CHANGE. */
- case 0xC0: /* PROGRAM CHANGE. */
- case 0xD0: /* CHANNEL AFTERTOUCH. */
- case 0xE0: /* PITCH-WHEEL. */
- /* We echo if we have a valid output cable. Note that we
- *don't* echo if we're capturing SysEx - this is because
- (for example) we might send patch changes in our unload
- routine, and having them cause non-SysEx junk to come
- back (e.g. VFX Presets) and get echoed will screw things
- quite royally. */
- cable = G.output.echoRoute.cable;
- if ((cable != NONE) && (!G.capture.active)) {
- outPacket = *inPacket; /* Copy the whole record (!) */
- outPacket.data[0] =
- status0 + (G.output.echoRoute.channel-1);
- /* Channelise... */
- refNum = G.output.refNum[cable];
- err = MIDIWritePacket(refNum, &outPacket);
- if (err != noErr)
- postError(ECHOERR_index, err);
- }
-
- break;
-
- case 0xF0: /* SYSTEM MESSAGE... */
- switch (status) {
- /* SYSTEM COMMON: */
- case 0xF0: /* START OF EXCLUSIVE. */
- dealWithSysEx(inPacket);
- break;
-
- case 0xF7: /* END OF EXCLUSIVE. */
- postError(DATAF7_index, 0);
- /* F7 as the status of a non-continuation? */
- break;
-
- case 0xF1: /* MIDI TIME CODE 1/4 FRAME. */
- case 0xF2: /* SONG POSITION POINTER. */
- case 0xF3: /* SONG SELECT. */
- case 0xF4: /* undefined. */
- case 0xF5: /* undefined. */
- case 0xF6: /* TUNE REQUEST. */
-
- /* 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. */
- break; /* Ignore all this junk. MIDI Manager
- handles most of it anyway. */
- }
- break;
-
- default: /* Not a status byte? */
- postError(DATA0_index, (int) status);
- }
- break;
-
- case midiStartCont: /* These must all be SysEx fragments. */
- case midiMidCont:
- case midiEndCont:
- dealWithSysEx(inPacket);
- }
- }
-
- /* This is the read hook. */
-
- LOCAL pascal int myReadHook(MIDIPacketPtr myPacket, long myRefCon)
- /* The refCon is the saved A5 for this context. */
- {
- long safeA5 = SetA5(myRefCon); /* Is "SetA5()" in Inside Mac anywhere? */
- Byte flags;
-
- if (G.lockDepth > 0) { /* We're locked. Catch it next time. */
- (void) SetA5(safeA5);
- return midiKeepPacket;
- } else {
- flags = myPacket->flags;
- switch (flags&midiTypeMask) {
- case midiMsgType: /* MIDI data. */
- dealWithInput(myPacket);
- break;
-
- case midiMgrType: /* Message from MIDI manager. */
- postError(UNEXPECTEDMSG_index,
- *((int *) &myPacket->data[0])
- ); /* data[0..1] is a word containing the error. */
- break;
-
- default:
- break; /* Ignore unknown message types. */
- }
-
- (void) SetA5(safeA5);
- return midiMorePacket;
- }
- }
-
- /* This is the time hook procedure. When it's called, it outputs any messages
- which are behind the current time. If that exhausts the messages, it just
- schedules itself for the next poll. Otherwise, it looks at the next message,
- and schedules a wake-up for itself at that time it.
- I would have liked to perform the trick used by MIDIArp and launch
- packets slightly ahead of time, but they might be continuation packets and
- might interfere with anything echoed directly by the read-hook.
- Invariant: the messages in the output queue are in time-ascending
- order. */
-
- /* It's an important property that the readHook
- doesn't echo SysEx messages; otherwise, we'd have to have some safe way of
- waiting for it to come out of any run of continued packets. As it is,
- with have to deal with the inverse case; when we want to transmit a long
- message, we must lock out the readHook so that it doesn't insert one of its
- own messages in the middle. */
-
- LOCAL pascal void myTimeHook(long currTime, long refCon)
- {
- long safeA5 = SetA5(refCon); /* Is "SetA5()" in Inside Mac anywhere? */
- int i;
- OutputSlotPtr slot;
- MIDIPacketPtr packet;
- long nextTStamp;
- int refNum;
- Byte cont;
- OSErr err;
-
- #if 0
- postDiagnostic("time hook", 0L, 0L);
- #endif
-
- for (;;) {
- if (G.outputQueue.consume == G.outputQueue.produce) {
- if (G.pollingVersion)
- MIDIWakeUp(G.timebase.refNum, currTime + POLL_TIME,
- 0, (ProcPtr) &myTimeHook
- ); /* Schedule ourself for another poll. */
-
- break;
- } else {
- slot = &G.outputQueue.queue[G.outputQueue.consume];
- packet = &slot->packet;
- nextTStamp = packet->tStamp;
- if (nextTStamp <= currTime) {
- #if 0
- postDiagnostic("time: transmitting", currTime, 0L);
- #endif
- /* Transmit the packet. */
- refNum = G.output.refNum[slot->cable];
-
- /*VORSICHT: we have to be careful of continuation flags. If we
- have a number of contination packets with widely-spaced
- timestamps, we don't want the echo hook to interfere. So, we
- must do some locking (before startCont and after endCont). */
-
- cont = packet->flags&midiContMask;
-
- if (cont == midiStartCont) LOCK;
-
- err = MIDIWritePacket(refNum, packet);
- if (err != noErr) postError(WRITEPACKET_index, err);
- #if 0
- postDiagnostic("xmit: flags, data[0..3]",
- (long) packet->flags,
- (long) *((long *)(&packet->data[0]))
- );
- #endif
-
- if (cont == midiEndCont) UNLOCK;
-
- G.outputQueue.consume = (G.outputQueue.consume+1) % MAX_QUEUE;
- /* Step onto the next message. */
- } else { /* Next message is too far ahead... */
- #if 0
- postDiagnostic("time: too far ahead", currTime, nextTStamp);
- #endif
- MIDIWakeUp(G.timebase.refNum, nextTStamp,
- 0, (ProcPtr) &myTimeHook
- ); /* Schedule ourself for the next one... */
- break; /* ..and return. */
- }
- }
- }
-
- (void) SetA5(safeA5);
- }
-
- /* Now we're out of the seat-of-the-pants interrupt stuff and into normal
- Class methods. */
-
- /* IMIDI (and friends) mustn't generate diagnostics - see IAnodyneApp(). */
-
- NEW void CMIDI::IMIDI(Boolean pollingVersion)
- {
- Handle myIcon;
- MIDIPortParams params;
- OSErr err;
- int i;
- StringPtr name;
-
- #ifdef ENABLED
- /*Set up the G values. */
- G.pollingVersion = pollingVersion;
- G.lockDepth = 0;
-
- G.capture.active = FALSE;
- G.capture.error = FALSE;
- G.capture.finished = FALSE;
- G.capture.base = NULL;
-
- G.output.echoRoute.cable = NONE;
- G.output.echoRoute.channel = 0;
- G.output.xmitRoute.cable = NONE;
- G.output.xmitRoute.channel = 0;
-
- G.error.index = 0;
- G.error.extra = 0;
- G.diags.produce = 0;
- G.diags.consume = 0;
-
- G.dispatchTime = 0L;
-
- G.outputQueue.queue =
- (OutputSlotPtr)
- gUtility->MustNewPtr(((long) MAX_QUEUE) * sizeof(OutputSlotRec));
-
- G.outputQueue.produce = 0;
- G.outputQueue.consume = 0;
-
- /*Get an ICON (in fact, the first one in our bundle ICN#) */
- myIcon = gUtility->MustGetResource('ICN#', BUNDLE_icns);
-
- /*Do we have the MIDI Manager installed? */
- gUtility->Assert("Can't connect to the MIDI Manager",
- SndDispVersion(midiToolNum) != 0L
- );
-
- /*Try to sign in. */
- name = gUtility->ApplicationName();
- g_Diagnostic("Signing in \"%#s\"", name);
- if (name[0] > 31) g_Die("MIDISignIn name: \"%#s\"", name);
-
- gUtility->HardCheckOSError(MIDISignIn(SIGNATURE, 0L, myIcon, (StringPtr) "\pHello"));
-
-
- /*First of all, create and add the (invisible) timebase port. We use this
- for timestamping output messages. */
-
- params.portID = ANODYNE_TIME;
- params.portType = midiPortTypeTimeInv;
- params.timeBase = 0; /* A timebase with a timebase? You jest, Sir. */
- params.readHook = NULL;
- params.initClock.sync = midiInternalSync;
- params.initClock.curTime = 0L;
- params.initClock.format = midiFormatMSec;
-
- params.refCon = SetCurrentA5();
-
- GetIndString(params.name, MIDIMANAGER_strs, TIMEBASE_index);
-
- err = MIDIAddPort(SIGNATURE, BUFSIZE, &G.timebase.refNum, ¶ms);
- if (err != midiVConnectMade) gUtility->HardCheckOSError(err);
-
- /*Add the input port. Note that we give it a timebase - that's just so that
- we get another chance at any packets we turn down from the readhook. */
-
- params.portID = ANODYNE_IN;
- params.portType = midiPortTypeInput;
- params.timeBase = G.timebase.refNum;
- params.offsetTime = 0L;
- params.readHook = (Ptr) &myReadHook;
-
- GetIndString(params.name, MIDIMANAGER_strs, INPUTCABLE_index);
-
- err = MIDIAddPort(SIGNATURE, BUFSIZE, &G.input.refNum, ¶ms);
- if (err != midiVConnectMade) gUtility->HardCheckOSError(err);
-
- /*Connect the output ports. Not many of the fields have to change in the
- params record. */
-
- params.portType = midiPortTypeOutput;
- params.timeBase = G.timebase.refNum;
- params.readHook = 0L;
- params.refCon = 0L;
-
- for (i = 0; i < NUM_CABLES; i++) {
- params.portID = ANODYNE_OUT1 + i; /* 'Out1', 'Out2' etc. */
- GetIndString(params.name, MIDIMANAGER_strs, OUTPUT1STCABLE_index + i);
- err = MIDIAddPort(SIGNATURE, BUFSIZE,
- &G.output.refNum[i], ¶ms
- );
- if (err != midiVConnectMade) gUtility->HardCheckOSError(err);
- }
-
- PatchMeIn();
-
- /*Start the clock. */
- MIDIStartTime(G.timebase.refNum);
-
- if (G.pollingVersion)
- MIDIWakeUp(G.timebase.refNum, 0L,
- (long) POLL_TIME, (ProcPtr) &myTimeHook
- ); /* Kick off the output routine. */
- #endif
- }
-
- OVERRIDE void CMIDI::Dispose()
- {
- #ifdef ENABLED
- SetCursor(*gWatchCursor);
- CoolOff();
-
- MIDISignOut(SIGNATURE);
- #endif
- inherited::Dispose();
- }
-
- /* StatusCheck(): the application calls this method periodically to make sure
- the MIDI module is happy. StatusCheck is also responsible for managing the
- asynchronous UNLOAD's - it repaints the dashboard status bar, and has the
- responsibility of creating a new patch file when a capture completes. */
-
- NEW void CMIDI::StatusCheck()
- {
- Str255 buff;
-
- #ifdef ENABLED
- /*Check for diagnostics first. */
- while (G.diags.consume != G.diags.produce) {
- g_Diagnostic("StatusCheck: %s [%08lx] [%08lx]",
- G.diags.buffer[G.diags.consume].mesg,
- G.diags.buffer[G.diags.consume].extra1,
- G.diags.buffer[G.diags.consume].extra2
- );
- G.diags.consume = (G.diags.consume+1) % MAX_DIAGS;
- }
-
- /*Now, check the status of the capture. If active, repaint status bar... */
- if (G.capture.active) {
- gDashboard->StatusBar(G.capture.spaceTotal - G.capture.spaceLeft,
- G.capture.spaceTotal
- );
- } else if (G.capture.finished) { /* Capture finished? */
- G.capture.finished = FALSE; /* Clear down flag. */
- gDashboard->NoStatusBar();
-
- if (G.capture.spaceLeft != 0L)
- postError(SIZEMISMATCH_index, 0);
- else if (G.capture.error)
- G.capture.error = FALSE; /* And just trash the data. */
- else
- G.capture.instrument->DigestDump(G.capture.base);
-
- DisposPtr(G.capture.base);
- G.capture.base = NULL;
- }
-
- if (G.error.index != 0) {
- gUtility->Notify(MIDIMANAGER_strs, G.error.index, G.error.extra);
- G.error.index = 0;
- }
- #endif
- }
-
- /* Transmit() and Pause() are the methods used to send out messages. We put
- all messages into an output buffer which is serviced by the timer hook.
- All messages are sent at the current time (w.r.t. our timebase) or perhaps
- into the future - all that Pause() does is advance the dispatch time to
- delay the next packet's transmission time.
- We do our own output buffering because we have no idea how much input
- buffering the destination has. We could send an entire patch bank, with
- correctly staggered timestamps, in one go, but they'd almost certainly
- overrun a buffer somewhere. Our output buffer is (of course) limited in
- size - if we fill it up, we have no alternative but to spin-wait for a
- slot to become free. The idea is that single patches can be dispatched and
- not waited for (good for the program's response to the user), but entire
- patch banks will be waited for until they're (almost) concluded. */
-
- NEW void CMIDI::Pause(int msec)
- {
- #ifdef ENABLED
- long currTime = MIDIGetCurTime(G.timebase.refNum);
-
- /*Bring dispatch time up-to-date, if necessary. */
- G.dispatchTime = Max(G.dispatchTime, currTime);
-
- G.dispatchTime += msec;
- #endif
- }
-
- PRIVATE void CMIDI::Dispatch(RouteRec route, MIDIPacketPtr p, Byte cont)
- {
- long currTime;
- OutputSlotPtr slot;
- int followingSlot;
-
- #if 0
- g_Diagnostic("Dispatch (cable=%d, continuation=%02x)",route.cable, cont);
- #endif
-
- followingSlot = (G.outputQueue.produce+1) % MAX_QUEUE;
- while (followingSlot == G.outputQueue.consume)
- /*Nothing*/; /* We spin-wait while the next packet would
- make the queue pointers coincide (yup,
- we can't distinguish empty vs. full!).*/
-
- slot = &G.outputQueue.queue[G.outputQueue.produce];
-
- gUtility->Assert("Dispatch(NOWHERE)", route.cable != NONE);
- slot->cable = route.cable;
- currTime = MIDIGetCurTime(G.timebase.refNum);
-
- /*Bring dispatch time up-to-date, if necessary. */
- #if 0
- g_Diagnostic("Bring up to date (dispatch=%08lx, current=%08lx)",
- G.dispatchTime, currTime
- );
- #endif
- G.dispatchTime = Max(G.dispatchTime, currTime);
-
- p->flags = cont | midiTimeStampValid;
- p->tStamp = G.dispatchTime;
-
- slot->packet = *p; /* Copy packet record to output buffer. */
-
- G.outputQueue.produce = followingSlot;
-
- if (!G.pollingVersion) {
- MIDIWakeUp(G.timebase.refNum, NULL, NULL, NULL);
- /* We *must* cancel any wake-up call because we're
- about to call it at non-interrupt, and don't
- want two at once...! */
-
- myTimeHook(currTime, SetCurrentA5());
- }
- }
-
- PRIVATE void CMIDI::Transmit(RouteRec route, Byte *mesg,
- long len, Boolean showStatus
- )
- {
- int thisTime;
- Byte cont;
- Byte *ptr;
- MIDIPacket packet;
- long remaining = len;
-
- #ifdef ENABLED
- #if 0
- g_Diagnostic("Transmit(len=%ld, mesg=[%02x, ...]", len, *mesg);
- #endif
-
- if (len <= MAX_MESSAGE) { /* Can do in a single packet. */
- BlockMove(mesg, &packet.data[0], len);
- packet.len = len + PACKET_JUNK;
- Dispatch(route, &packet, midiNoCont);
- Pause(len / BYTES_PER_MSEC);
- } else {
- cont = midiStartCont;
- ptr = mesg;
- remaining = len;
- while (remaining > 0) {
- thisTime = Min(remaining, MAX_MESSAGE);
- BlockMove(ptr, &packet.data[0], thisTime);
- packet.len = thisTime + PACKET_JUNK;
- Dispatch(route, &packet, cont);
- ptr += thisTime;
- remaining -= thisTime;
- cont = (remaining > MAX_MESSAGE) ? midiMidCont : midiEndCont;
- /*Now, we place a short pause before the next packet (if any), just
- to avoid SCC overruns. */
- Pause(thisTime / BYTES_PER_MSEC);
- if (showStatus) gDashboard->StatusBar(ptr - mesg, len);
- }
- }
- #endif
- }
-
- NEW void CMIDI::Silence()
- {
- Byte mesg[3];
-
- if (G.output.echoRoute.cable != NONE) {
- mesg[0] = CONTROL_CHANGE + (G.output.echoRoute.channel - 1);
- mesg[1] = ALL_NOTES_OFF;
- mesg[2] = 0x00;
- Transmit(G.output.echoRoute, mesg, 3, FALSE);
- }
- }
-
- NEW void CMIDI::SetOutput(RouteRec route)
- {
- G.output.xmitRoute = route;
- }
-
- NEW void CMIDI::ClearOutput()
- {
- G.output.xmitRoute.cable = NONE;
- G.output.xmitRoute.channel = 0;
- }
-
- /* Select a default for midi idling and for messages; or, nothing (if cable
- is NONE). The lock is just to stop any note-on messages sneaking in after
- we've silenced the channel. */
-
- NEW void CMIDI::StartEchoing(RouteRec route)
- {
- LOCK;
-
- if (route.cable != NONE && (route.cable < 0 || route.cable >= NUM_CABLES))
- g_Die("CMIDI::Select: bad cable (%d)", route.cable);
-
- if (route.cable != G.output.echoRoute.cable
- || route.channel != G.output.echoRoute.channel
- )
- Silence();
-
- G.output.echoRoute = route;
-
- UNLOCK;
- }
-
- NEW void CMIDI::StopEchoing()
- {
- RouteRec route;
-
- route.cable = NONE;
- route.channel = 0;
- StartEchoing(route);
- }
-
- NEW void CMIDI::PatchChange(int patch)
- {
- Byte mesg[2];
-
- mesg[0] = PATCH_CHANGE + (G.output.xmitRoute.channel - 1);
- mesg[1] = patch;
- Transmit(G.output.xmitRoute, mesg, 2, FALSE);
- }
-
- PRIVATE long CMIDI::CalcSysExLength(Byte *mesg)
- {
- Byte *p = mesg;
-
- while (*p != EOX) p++;
- return((p - mesg) + 1);
- }
-
- /* SendSysEx() - we have the option of displaying a status bar *just for this
- SysEx*, in case we want to do that for a very long one. */
-
- NEW void CMIDI::SendSysEx(Byte *mesg, Boolean showStatus)
- {
- Transmit(G.output.xmitRoute, mesg, CalcSysExLength(mesg), showStatus);
- if (showStatus) gDashboard->NoStatusBar();
- }
-
- NEW void CMIDI::InitiateCapture(CInstrument *instrument)
- {
- #ifdef ENABLED
- Byte *buff = (Byte *) gUtility->MustNewPtr(instrument->env.captureSize);
-
- G.capture.instrument = instrument;
- G.capture.howMany = instrument->env.numMessages;
- G.capture.spaceTotal = instrument->env.captureSize;
- G.capture.spaceLeft = G.capture.spaceTotal;
- G.capture.base = buff;
- G.capture.ptr = buff;
- G.capture.active = TRUE; /* Let rip... */
-
- gDashboard->StatusBar(0, 1); /* Draw (empty) status bar... */
- instrument->RequestDump(); /* Just in case that's needed. */
- #endif
- }
-
- /* CancelCapture() - Now presumably, the application called CMIDI::Unloading()
- and got back a reply of TRUE, and called us here. Now,
- the capture might have just finished (finished==TRUE or
- error==TRUE), or it might still be going (active==TRUE).
- Whichever way, we should be able to close it down and
- dispose of the buffer. This *wouldn't* be the case if
- there was a call to CMIDI::StatusCheck() in the middle.
- StatusCheck() sets the buffer base to NULL to allow us a
- sanity check, since unfortunately, we can't safely do
- a check by look at the flags - they may get nuked
- at interrupt level while we're looking at them...! */
-
- NEW void CMIDI::CancelCapture()
- {
- gUtility->Assert("CancelCapture", G.capture.base != NULL);
-
- G.capture.active = FALSE; /* First, pull the plug. */
- G.capture.finished = FALSE;
- G.capture.error = FALSE; /* Don't confuse next call to
- StatusCheck(). */
-
- DisposPtr(G.capture.base);
- G.capture.base = NULL;
-
- gDashboard->NoStatusBar(); /* Turn off the thermometer. */
- }
-
- /* CoolOff() - if we do a LOAD, the output buffer might still have some stuff
- in it which is waiting for transmission. Hitting QUIT right
- away might lose the tail-end of a bank transmission. So, just
- before quitting, we call CoolOff() to spin-wait until the output
- buffer is empty. */
-
- NEW void CMIDI::CoolOff()
- {
- #ifdef ENABLED
- while (G.outputQueue.consume != G.outputQueue.produce)
- SystemTask();
- #endif
- }
-
- /* Unloading() - we don't allow a Load() while we're unloading (contention
- for the thermometer; besides, it'll overrun our buffers).
- Also, if we're unloading, we enable CANCEL. Vorsicht: just
- because you call Unloading() and get TRUE, doesn't mean it
- will be true a moment later. Blocking LOAD is harmless, but
- be careful about CANCEL. Oh yes, allowing UNLOAD while
- unloading is pretty much a no-no, as well. */
-
- NEW Boolean CMIDI::Unloading()
- {
- return G.capture.active;
- }
-
- /* PatchMeIn(): shouldn't go into the final release, but convenient for
- testing. Connects up to the Apple MIDI Drivers. */
-
- PRIVATE void CMIDI::CheckConnect(OSErr err)
- {
- if (err != midiVConnectErr)
- gUtility->HardCheckOSError(err);
- }
-
- PRIVATE void CMIDI::PatchMeIn()
- {
- /*Both real input ports to my single input port: */
- CheckConnect(MIDIConnectData(APPLE_MIDI_DRIVER, APPLE_DRIVER_IN1,
- SIGNATURE, ANODYNE_IN
- )
- );
- CheckConnect(MIDIConnectData(APPLE_MIDI_DRIVER, APPLE_DRIVER_IN2,
- SIGNATURE, ANODYNE_IN
- )
- );
-
- /*Each of my output ports to the corresponding real output: */
- CheckConnect(MIDIConnectData(SIGNATURE, ANODYNE_OUT1,
- APPLE_MIDI_DRIVER, APPLE_DRIVER_OUT1
- )
- );
- CheckConnect(MIDIConnectData(SIGNATURE, ANODYNE_OUT2,
- APPLE_MIDI_DRIVER, APPLE_DRIVER_OUT2
- )
- );
- }
-