TweetFollow Us on Twitter

Midi Lib
Volume Number:3
Issue Number:7
Column Tag:Assembly Lab

A Midi Library for Pascal

By Kirk Austin, MacTutor Contributing Editor, Austin Development, San Rafael, CA

What is MIDI?

Before I get too much into the nuts and bolts of this whole thing perhaps we should take a look at what it’s all about. MIDI is an acronym for Musical Instrument Digital Interface, and really came into being somewhere around 1983. Originally, it was created to allow music synthesizers to communicate with each other, but there was enough foresight in the minds of the originators to leave room for future enhancements. As far as the scope of this article is concerned, the most important thing about MIDI is that it allows music synthesizers to communicate with computers, specifically, the Macintosh.

The need for a standard

To really understand why MIDI came about, you have to know a little bit about the history of music synthesizers. In the late 1960’s synthesizers were, for the most part, voltage controlled devices. That is, you could control the frequency of an oscillator (a tone generating device) by varying a DC voltage that was routed to one of its control inputs. The higher the voltage, the higher the note and vice versa. The standard that was used by companies like Moog and ARP was 1 volt/octave. This meant that if your control voltage changed from 4 volts to 5 volts the oscillator would shift its pitch higher by one octave.

This “voltage control” concept worked pretty well at the time, but you have to remember that the hardware itself was pretty primitive by today’s standards. For instance, most synthesizers in that era could only play one note at a time. Chords could only be created by using a multitrack tape recorder and overdubbing the different notes. This was how recordings like “Switched On Bach” were produced.

Now, when you’re only dealing with a note at a time things aren’t too complicated. Still, you had to make sure all of your oscillators were in tune, because typically you would have to use more than one oscillator to produce a respectable sounding note. Then all of the oscillators would have to be scaled so that they would track accurately. These last two points were no small problem, because the analog oscillators at that time had a very large problem -- thermal drift. This meant that you could tune and scale all of the oscillators very carefully, and 5 minutes later they would be out of calibration because the temperature of the semiconductor junctions had changed. Ahh, those were fun days.

But, those problems aside, there were other signals that were needed to produce a note besides just a control voltage for the oscillator. You also needed a trigger pulse to tell the synthesizer when to start playing a note. Then you needed a way to let the synthesizer know that you wanted to stop a note when you lifted your finger from the keyboard. This was usually in the form of a “gate” signal. Okay, so now we’re up to three signals just to produce one note at a time. Then, as if that weren’t enough, some manufacturers were using a positive going pulse as the trigger and others were using a negative going pulse. You could get around this problem with special adaptor boxes and the like, but then a much larger problem came looming over the horizon -- polyphony.

Polyphony means the ability to play more than one note at a time, and even though it was a tremendous breakthrough for the musician, it multiplied the problems for electronic musical instrument designers. Now, to the best of my knowledge, the polyphonic synthesizer keyboard that we know and love today came into being around 1978 thanks to the advent of the microprocessor and the talents of a couple of guys named Dave Rossum and Scott Wedge of Emu Systems. Their ideas led to the use of microprocessor based keyboards by virtually all of the synthesizer manufacturers. Oberheim was one of the first companies to bring out a polyphonic instrument. It had a keyboard that was scanned by the microprocessor which then converted the information into DC control voltages and gate signals for controlling its analog oscillators, filters, and amplifiers. The amazing thing about this instrument was that it actually worked, and it provided a great leap forward for synthesizers in general. But, now another problem began to appear.

Musicians wanted to have a remote keyboard controller that could be worn around their neck and send signals down a cable to their synthesizers which might be offstage somewhere. Or maybe they didn’t want a keyboard at all! Maybe they wanted to control a synthesizer from a guitar or a drum set! Instrument designers were really starting to get overwhelmed by all of the options that musicians were demanding at this point, and it became clear that there was a need for some kind of standard way for controllers (keyboards, guitars, drums) and synthesizers (the sound producing electronics) to communicate with each other so that instruments made by different manufacturers could work together.

MIDI is born

In late 1981 a paper was presented to the Audio Engineering Society suggesting a digital, serial interface for electronic music synthesizers. This scheme was referred to as the Universal Synthesizer Interface, and was authored by Dave Smith and Chet Wood of Sequential Circuits. This proposal was, in fact, the precursor to MIDI, and served as the impetus to get manufacturers of electronic musical equipment to talk with each other about some sort of communications standard. What finally came out of all of the discussions was the MIDI specification 1.0.

The data not the sound

Now, probably the most confusing thing about MIDI to the beginner is understanding that MIDI is concerned with control data, and not the actual sound itself. For instance, if we talk about a MIDI recorder that emulates many of the functions of a traditional tape recorder you must understand that the MIDI information that is being recorded is simply the note on and note off signals. When a key is pressed on a synthesizer keyboard 3 bytes of MIDI information are sent over the serial connection telling the sound producing electronics to start playing a note (the details of these 3 bytes will be explained shortly). When that same key is finally released another 3 bytes of information is sent over the MIDI cable telling the sound producing electronics to stop playing that note. As you can readily conclude from this simple example, a note of any length requires the same amount of information -- 6 bytes. This is what makes for such compact use of memory in MIDI recorders. By comparison, actually recording the sound itself by an analog to digital conversion would take tens of thousands of bytes even for a very short sound, and a longer sound would require more memory still. But, even more importantly, the use of MIDI control signals allows musicians to factor out the actual sound from the note choices and timing information. This means that I can play a part on a piano-style keyboard, record it on a MIDI recorder, and then play it back with the ability to change the actual sound as it is playing. So now what I recorded as a piano sound can be played back as a trumpet sound, or a violin sound, or a marimba sound. This is what gives tremendous power to MIDI recorders. A musician can enter in all of the notes without prior knowledge of what the final arrangement is going to be, then change instruments on the fly to help with the decision making process that is necessary to create a final arrangement. In this regard, the MIDI recorder is to the traditional tape recorder as the Word Processor is to the typewriter. It is a powerful tool that goes far beyond emulating its traditional counterpart.

The command set

Here are a few of the more common MIDI commands that are used to control synthesizers. For a copy of the full specification (which is much longer than this article allows), write to:

International MIDI User’s Group

8426 Vine Valley Dr.

Sun Valley, CA 91352

Note on

This is the data that is sent when a key goes down on a synthesizer keyboard.

 %1001nnnn
 Where nnnn is the MIDI Channel Number (0-15)
 %0kkkkkkk
 Where kkkkkkk is the Key Number (0-127) 60 = middle C
 %0vvvvvvv
 Where vvvvvvv is the velocity

Note off

This is the data that is sent when a key is released on a

synthesizer keyboard.

 %1000nnnn
 Where nnnn is the MIDI Channel Number (0-15)
 %0kkkkkkk
 Where kkkkkkk is the Key Number (0-127) 60 = middle C
 %0vvvvvvv
 Where vvvvvvv is the velocity

All notes off

This is a useful command that is typically sent when a sequence is terminated before the end of the file. This command turns off all of the notes that are playing to avoid the disastrous problem of “stuck notes” (what happens when a note on command is not followed by a corresponding note off command).

%1011nnnn
 Where nnnn is the MIDI Channel Number (0-15)
 %01111011
 %00000000

Aftertouch

This is the data that is sent when pressure is applied to the synthesizer keyboard while the key is being held down.

 %1101nnnn
 Where nnnn is the MIDI Channel Number (0-15)
 %0vvvvvvv
 Where vvvvvvv is the amount of aftertouch

Program change

This command changes the current sound that the synthesizer is playing.

 %1100nnnn
 Where nnnn is the MIDI Channel Number (0-15)
 0ppppppp
 Where ppppppp is the Program Change Number (0-127)

Pitch Wheel

This is the data that is sent when the pitch bend wheel on a synthesizer is moved from its center position.

 %1110nnnn
 Where nnnn is the MIDI Channel Number (0-15)
 %0vvvvvvv
 Where vvvvvvv is the LSB of the Pitch Wheel Change
 %0vvvvvvv
 Where vvvvvvv is the MSB of the Pitch Wheel Change

Continuous controllers

These are generalized controllers that could correspond to knobs on a particular synthesizer. Continuous controller #1 is the Modulation Wheel by default.

 %1011nnnn
 Where nnnn is the MIDI Channel Number (0-15)
 %0ccccccc
 Where ccccccc is the control number (0-121)
 %0vvvvvvv
 Where vvvvvvv is the control value

The serial ports

The Macintosh serial ports were not really constructed with MIDI in mind, unfortunately, but you can make them work by using a few special techniques that we’ll talk about now.

Hardware

The outputs of the Mac serial ports have to be level shifted and translated to a standard TTL gate output in order to meet the MIDI spec. The inputs have to be optoisolated. If you want a quick and dirty schematic of the necessary hardware check MacTutor’s October 1985 issue, or The Best of MacTutor, volume 1.

Software

The timing of MIDI information is one of the more difficult demands that the Macintosh has to deal with. I mean, it doesn’t really matter if your spreadsheet calculates a formula in 2 seconds or 2.02 seconds, but that much difference in a musical performance is totally unacceptable. In order for the information to be processed as quickly as possible we have to use interrupts to handle tasks like transmitting and receiving bytes of MIDI data, and updating the timing reference.

Time-Stamping

Now, just sending and receiving bytes of MIDI information is just fine for a lot of applications, but typically you want to not only get the incoming data, but also know when it arrived. This is necessary for applications like recorders where you log the time that the data came in so you can play it back with the correct timing. Sounds simple huh? Well, it is and it isn’t. For one thing, in order to accurately record the time you have to use a technique called “time-stamping”, and you have to do it on an interrupt level. That is, when the interrupt routine is called because the SCC chip has a byte that has just arrived at the Mac, the routine not only has to get that byte from the SCC chip and place it in a buffer area, but it also has to get a counter value (that has been set up previously) and tag it onto the incoming MIDI byte. Then when it’s time to play the data back the counter is started up and you sit there and watch the counter for each byte of information to be sent out at the appropriate time (this is a really crude example involving no data compression).

Okay, not to difficult, but it gets more complicated. To set up a counter that you can use to reference all of this stuff to you have to use one of the timers in the 6522 chip. There are two timers, appropriately called timer1 and timer2. Unfortunately, timer1 is the most accurate timer to use if you want to create continuous interrupts at a specified time interval (the interval we will be using will be in the millisecond range). This is because timer1 automatically reloads itself after it times out. Timer2 requires that your interrupt routine reload it after it times out, and you never know how long it is going to take before your interrupt routine can respond to a timer2 interrupt. Now, if your timing doesn’t have to be that accurate you can go ahead and use timer2, in fact, this is the way that the Time Manager routines in Inside Macintosh vol. 4 appear to work. So, you can simply use the Time Manager routines if you find that to be easier, and aren’t that concerned with absolute timing accuracy.

The reason that you might want to use timer2 it that timer1 is used by the Sound Manager routines, so if you are using timer1 for time-stamping incoming MIDI data you can’t use any of the Sound Manager routines, you have to write to the sound hardware directly in order to produce a click track or whatever. Anyway, that’s the tradeoff. I have written the following code using timer1 since you can simply ignore it if you want to use the Time Manager routines and everything will be fine. If you want to use timer1 call InitTimer at the beginning of your application, and QuitTimer at the end of it. Conversely, if you don’t want to use it don’t call either of those routines (or any of the other timer/counter routines).

Overview

Okay, here’s how you go about using these routines for MIDI software. Now, let me say in advance that I know you’re not supposed to write data to your own code segments, but I did it this way because alot of people complained about the use of (A5) variables in the November 1985 MacTutor article. In a future article I’ll present another way to do MIDI that doesn’t use the approach I’m using here, but for now, this should get you going since the problems won’t appear until the Macintosh II starts using the memory management chip.

Initialization

When your application starts up it should call InitSCCA and/or InitSCCB depending on whether you are going to use one or both channels (A is the modem port, B is the printer port). If you are going to use the counter then you should also call InitTimer when your application starts up.

Receiving data

To receive a MIDI byte call RxMIDIA or RxMIDIB depending on which port you are using. When you call these routines you must leave space on the stack for a longword result. The MIDI byte is in the lower 8 bits of the longword, and the upper 3 bytes contain the value of the counter when the byte arrived at the SCC chip.

Transmitting data

To send a byte of MIDI data use the routines TxMIDIA or TxMIDIB depending on which port you want to use. To use these routines simply place a word on the stack with the MIDI byte in the lower 8 bits and call the appropriate routine.

The Counter

To set the counter to a value of 1 call StartTimer. The value of 1 is used instead of 0 because the 0 value is used as a special flag. You should know that the counter value defaults to 1 when your application starts up.

To get the current value of the counter call GetCounter. This routine requires that you leave space on the stack for a longword result. The longword contains the counter value.


UNIT LSPMIDI;

INTERFACE
 PROCEDURE InitSCCA;
 {call this once at the beginning of your application if} 
 {you are going to use the modem port for MIDI}

 PROCEDURE TxMIDIA (TheData : integer);
 {use this procedure to transmit a byte of MIDI data }
 {through the modem port the MIDI byte is in the }
 {lower 8 bits of the word}

 FUNCTION RxMIDIA : LongInt;
 {use this function to get a byte of MIDI data and} 
 {the counter value associated}
 {with that byte through the modem port}
 {the MIDI byte is in the lower 8 bits of the longword}
 {the upper 3 bytes of the longword contain the counter }
 {value when the byte arrived at the Macintosh}

 PROCEDURE ResetSCCA;
 {call this procedure when your application is done if} 
 {you called InitSCCA at the beginning of your }
 {application or the system will crash}

 PROCEDURE InitSCCB;
 {call this once at the beginning of your application} 
 {if you are going to use the printer port for MIDI}

 PROCEDURE TxMIDIB (TheData : integer);
 {use this procedure to transmit a byte of MIDI data}
 {through the printer port the MIDI byte is in the lower }
 {8 bits of the word}

 FUNCTION RxMIDIB : LongInt;
 {use this function to get a byte of MIDI data and} 
 {the counter value associated}
 {with that byte through the printer port}
 {the MIDI byte is in the lower 8 bits of the longword}
 {the upper 3 bytes of the longword contain the counter}
 {value when the byte arrived at the Macintosh}

 PROCEDURE ResetSCCB;
 {call this procedure when your application is done }
 {if you called InitSCCB at the beginning of your}
 {application or the system will crash}

 PROCEDURE InitTimer (TimrValue : integer);
 {call this procedure once at the beginning of your}     {application 
if you are going to}
 {make use of time-stamping.  1 millisecond = decimal 782}

 PROCEDURE LoadTimer (TimrValue : integer);
 {call this procedure if you want to change the} 
 {interval of time that the counter}
 {is incremented.  1 millisecond = decimal 782}

 PROCEDURE StartCounter;
 {call this procedure to set the counter value to 1}

 FUNCTION GetCounter : LongInt;
 {call this function to get the current value} 
 {of the counter}

 PROCEDURE QuitTimer;
 {call this procedure when your application is done}
 {if you called InitTimer at}
 {the beginning of your application or the system }
 {will crash}

IMPLEMENTATION
{$A+}
 PROCEDURE InitSCCA;
 external;
 PROCEDURE TxMIDIA;
 external;
 FUNCTION RxMIDIA;
 external;
 PROCEDURE ResetSCCA;
 external;
 PROCEDURE InitSCCB;
 external;
 PROCEDURE TxMIDIB;
 external;
 FUNCTION RxMIDIB;
 external;
 PROCEDURE ResetSCCB;
 external;
 PROCEDURE InitTimer;
 external;
 PROCEDURE LoadTimer;
 external;
 PROCEDURE StartCounter;
 external;
 FUNCTION GetCounter;
 external;
 PROCEDURE QuitTimer;
 external;
{$A-}

END.

; Low Level MIDI routines with time-stamping
; Written by Kirk Austin 5/17/87
; This code is in the public domain and is absolutely free
; Note: Be sure and turn off range checking in LS Pascal
;       to prevent a crash. 

; Serial Chip equates
SCCRd   EQU $1D8
SCCWr   EQU $1DC
aData   EQU 6
aCtl    EQU 2
bData   EQU 4
bCtl    EQU 0
TBEEQU  2

;  Interrupt vector equates
Lvl1DT  EQU $192
Lvl2DT  EQU $1B2
RxIntOffsetAEQU  24
TxIntOffsetAEQU  16
SpecRecCondAEQU  28
RxIntOffsetBEQU  8
TxIntOffsetBEQU  0
SpecRecCondBEQU  12

;  6522 equates
VIAEQU  $1D4
vT1C    EQU $800
vT1CH   EQU $A00
vT1L    EQU $C00
vACR    EQU $1600
vIER    EQU $1C00

; XDEF all routines that need to be accessed externally.

 XDEF   InitSCCA
 XDEF   InitSCCB
 XDEF   TxMIDIA
 XDEF   TxMIDIB
 XDEF   RxMIDIA
 XDEF   RxMIDIB
 XDEF   ResetSCCA
 XDEF   ResetSCCB
 XDEF   InitTimer
 XDEF   LoadTimer
 XDEF   StartCounter
 XDEF   GetCounter
 XDEF   QuitTimer

; These are the routines for the Modem Port


;PROCEDURE InitSCCA;
; Call this routine at the beginning of your application if 
; using the modem port for MIDI information transfers.
InitSCCA
 MOVE   SR,-(SP) ; Save interrupts
 MOVEM.LD0/A0-A2,-(SP)  ; Save registers
 ORI    #$0300,SR; Disable interrupts

 MOVE.L SCCRd,A1 ; Get base Read address
 ADD    #aCtl,A1 ; Add offset for control
 MOVE.B (A1),D0  ; Dummy read
 MOVE.L (SP),(SP); Delay
 MOVE.L SCCWr,A0 ; Get base Write address
 ADD    #aCtl,A0 ; Add offset for control
 MOVE.B #9,(A0)  ; pointer for SCC reg 9
 MOVE.L (SP),(SP); Delay
 MOVE.B #%10000000,(A0) ; Reset channel
 MOVE.L (SP),(SP); Delay
 BSR    InitSCCChan; branch to common init routine

; set up the interrupt vectors

 MOVE.L #Lvl2DT,A0 ; get dispatch table ptr
 MOVE   #RxIntOffsetA,D0  ; get offset to Rx vector
 LEA    PRxIntHandA,A1  ; point to previous vector stor
 MOVE.L 0(A0,D0),(A1); save previous int vector
 LEA    RxIntHandA,A1; set Rx vector
 MOVE.L A1,0(A0,D0)
 MOVE   #TxIntOffsetA,D0  ; get offset to Tx vector
 LEA    PTxIntHandA,A1  ; point to previous vector stor
 MOVE.L 0(A0,D0),(A1); save previous int vector
 LEA    TxIntHandA,A1; set Tx vector
 MOVE.L A1,0(A0,D0)
 MOVE   #SpecRecCondA,D0  ; offset to Special vector
 LEA    StubA,A1
 MOVE.L A1,0(A0,D0)

; initialize the flags & pointers

 LEA    RxByteInA,A2 ; get the address
 CLR    (A2)
 LEA    RxByteOutA,A2; get the address
 CLR    (A2)
 LEA    RxQEmptyA,A2 ; get the address
 MOVE #$FFFF,(A2)
 LEA    TxByteInA,A2 ; get the address
 CLR    (A2)
 LEA    TxByteOutA,A2; get the address
 CLR    (A2)
 LEA    TxQEmptyA,A2 ; get the address
 MOVE #$FFFF,(A2)

 MOVEM.L(SP)+,D0/A0-A2  ; Restore registers
 MOVE   (SP)+,SR ; Restore interrupts
 RTS    ; and return

; This is the common initialzation routine for both channels

InitSCCChan
 MOVE.B #4,(A0)  ; pointer for SCC reg 4
 MOVE.L (SP),(SP); Delay
 MOVE.B #%10000100,(A0) ; 32x clock, 1 stop bit
 MOVE.L (SP),(SP); Delay
 MOVE.B #1,(A0)  ; pointer for SCC reg 1
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00000000,(A0) ; No W/Req
 MOVE.L (SP),(SP); Delay
 MOVE.B #3,(A0)  ; pointer for SCC reg 3
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00000000,(A0) ; Turn off Rx
 MOVE.L (SP),(SP); Delay
 MOVE.B #5,(A0)  ; pointer for SCC reg 5
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00000000,(A0) ; Turn off Tx
 MOVE.L (SP),(SP); Delay
 MOVE.B #11,(A0) ; pointer for SCC reg 11
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00101000,(A0) ; Make TRxC clock source
 MOVE.L (SP),(SP); Delay
 MOVE.B #14,(A0) ; pointer for SCC reg 14
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00000000,(A0) ; Disable BRGen
 MOVE.L (SP),(SP); Delay
 MOVE.B #3,(A0)  ; pointer for SCC reg 3
 MOVE.L (SP),(SP); Delay
 MOVE.B #%11000001,(A0) ; Enable Rx
 MOVE.L (SP),(SP); Delay
 MOVE.B #5,(A0)  ; pointer for SCC reg 5
 MOVE.L (SP),(SP); Delay
 MOVE.B #%01101010,(A0) ; Enable Tx and drivers
 MOVE.L (SP),(SP); Delay
 MOVE.B #15,(A0) ; pointer for SCC reg 15
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00001000,(A0) ; Enable DCD int for mouse
 MOVE.L (SP),(SP); Delay
 MOVE.B #0,(A0)  ; pointer for SCC reg 0
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00010000,(A0) ; Reset EXT/STATUS
 MOVE.L (SP),(SP); Delay
 MOVE.B #0,(A0)  ; pointer for SCC reg 0
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00010000,(A0) ; Reset EXT/STATUS again
 MOVE.L (SP),(SP); Delay
 MOVE.B #1,(A0)  ; pointer for SCC reg 1
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00010011,(A0) ; Enable interrupts
 MOVE.L (SP),(SP); Delay
 MOVE.B #9,(A0)  ; pointer for SCC reg 9
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00001010,(A0) ; Set master int enable
 MOVE.L (SP),(SP); Delay
 RTS
 
;PROCEDURE TxMIDIA (TheData : integer);
; This is the routine to transmit a MIDI byte of data
; through the Modem Port.To use this routine place 
; the byte to be transmitted as the lower 8 bits
; of a word on the stack, then call TxMIDIA.
TxMIDIA
 LINK   A6,#0    ; set frame pointer
 MOVE   SR,-(SP) ; Save interrupts
 MOVEM.LD0/A0-A3,-(SP)  ; Save registers
 ORI    #$0300,SR; Disable interrupts

 LEA    TxQEmptyA,A3 ; get the address
 TST.B  (A3); is TxQueue empty?
 BNE    TxQEA    ; if so branch
 LEA    TxByteInA,A3 ; get the address
 MOVE   (A3),D0  ; if not add byte to queue
 LEA    TxQueueA,A2; point to queue
 MOVE.B 9(A6),0(A2,D0)  ; place byte in queue
 ADDQ   #1,D0    ; update TxByteIn
 CMP    #$100,D0
 BNE    @1
 MOVE   #0,D0
@1 MOVE D0,(A3)
 BRA    TxExitA  ; and exit
 
TxQEA 
 MOVE.L SCCRd,A0 ; get SCC Read Address
 MOVE.L SCCWr,A1 ; get SCC Write address
 MOVE   #aCtl,D0 ; get index for Ctl
 BTST.B #TBE,0(A0,D0); transmit buffer empty?
 BNE    FirstByteA ; if so branch
 LEA    TxByteInA,A3 ; get the address
 MOVE   (A3),D0  ; if not add to queue
 LEA    TxQueueA,A2; point to queue
 MOVE.B 9(A6),0(A2,D0)  ; place byte in queue
 ADDQ   #1,D0    ; update pointer
 CMP    #$100,D0
 BNE    @1
 MOVE   #0,D0
@1 MOVE D0,(A3)
 LEA    TxQEmptyA,A3 ; get the address
 MOVE   #0,(A3)  ; reset queue empty flag
 BRA    TxExitA  ; and exit
 
FirstByteA
 MOVE   #aData,D0; get index to data
 MOVE.L (SP),(SP); delay
 MOVE.B 9(A6),0(A1,D0)  ; write data to SCC
 MOVE.L (SP),(SP); Delay
 

TxExitA 
 MOVEM.L(SP)+,D0/A0-A3  ; Restore registers
 MOVE   (SP)+,SR ; Restore interrupts
 UNLK   A6; release frame pointer
 MOVE.L (SP)+,A1 ; save return address
 ADD.L  #2,SP    ; move past data word
 MOVE.L A1,-(SP) ; put address back on stack
 RTS    ; and return

;FUNCTION RxMIDIA : LongInt;
; This routine gets a byte through the modem port. 
; To use this routine treat it like a Pascal 
; function.  Leave space on the stack for a longword
; of data before calling this routine.  If the data 
; on the stack after
; the routine executes is 0 there was no MIDI data available. 
; If it’s non-0 the upper 3 bytes contain the counter 
; value, the MIDI byte is the low byte.
RxMIDIA
 LINK   A6,#0    ; set frame pointer
 MOVE   SR,-(SP) ; Save interrupts
 MOVEM.LD0-D1/A0-A3,-(SP) ; Save registers
 ORI    #$0300,SR; disable interrupts

 LEA    RxQEmptyA,A3 ; get the address
 TST.B  (A3); any data available?
 BEQ    @1; if so, branch
 MOVE.L #0,8(A6) ; if not, return with 0
 BRA    RxExitA
@1 LEA  RxByteOutA,A3; get the address
 MOVE   (A3),D0  ; get index to byte out
 LEA    RxQueueA,A2; point to queue
 MOVE.L #0,D1    ; clear data register
 MOVE.L 0(A2,D0),D1; get MIDI data
 MOVE.L D1,8(A6) ; place on stack for return 
 ADDQ   #4,D0    ; update index
 CMP    #$400,D0
 BNE    @2
 MOVE   #0,D0
@2 LEA  RxByteOutA,A3; get the address
 MOVE   D0,(A3)
 LEA    RxByteInA,A3 ; get the address
 MOVE   (A3),D1
 CMP    D0,D1    ; is queue empty?
 BNE    RxExitA  ; if not exit
 LEA    RxQEmptyA,A3 ; get the address
 MOVE   #$FFFF,(A3); if empty, set flag

RxExitA 
 MOVEM.L(SP)+,D0-D1/A0-A3 ; Restore registers
 MOVE   (SP)+,SR ; restore interrupts
 UNLK   A6
 RTS    ; and return
 
; This is the interrupt routine for receiving through
; the modem port. It places the counter value and the 
; MIDI byte in a circular queue to be
; accessed later by the application.
; When the system gets this far, A0 contains the 
; SCC base read Ctl address
; and A1 contains the SCC base write Ctl address
; for this channel.The data addresses are offset by 4 
; from the control addresses.
; D0-D3/A0-A3 are already preserved, so they may 
; be used freely.
RxIntHandA
 MOVE   SR,-(SP)
 ORI    #$0300,SR; disable interrupts

@3 MOVE #4,D0    ; get data offset
 CLR.L  D1; prepare for data
 MOVE.L (SP),(SP); Delay
 MOVE.B 0(A0,D0),D1; read data from SCC
 MOVE.L (SP),(SP); Delay
 LEA    RxQueueA,A2; point to queue
 LEA    RxByteInA,A3 ; get the address
 MOVE   (A3),D0  ; get offset to next cell
 LEA    Counter,A3 ; get the address
 MOVE.L (A3),D2  ; put counter value in D2
 LSL.L  #8,D2    ; shift counter one byte
 ADD.L  D2,D1    ; combine counter and data
 MOVE.L D1,0(A2,D0); put longword in queue
 LEA    RxQEmptyA,A3 ; get the address
 MOVE   #0,(A3)  ; reset queue empty flag
 ADDQ   #4,D0    ; update index
 CMP    #$400,D0
 BNE    @1
 MOVE   #0,D0
@1 LEA  RxByteInA,A3 ; get the address
 MOVE   D0,(A3)

@2 BTST.B #0,(A0); is there more data?
 BNE    @3; do it again if there is

 MOVE   (SP)+,SR ; enable interrupts
 RTS    ; and return
 
; This is the interrupt routine for transmitting a byte
; through the modem port.It checks to see if there 
; is any data to send, and if there is it sends it to 
; the SCC.  If there isn’t it resets the TBE interrupt 
; in the SCC and exits.
; When the system gets this far, A0 contains the SCC
; base read Ctl address and A1 contains the SCC base 
; write Ctl address for this channel.
; The data addresses are offset by 4 from the control
; addresses. D0-D3/A0-A3 are already preserved, so 
; they may be used freely.
TxIntHandA
 MOVE   SR,-(SP)
 ORI    #$0300,SR; disable interrupts
 
 LEA    TxQEmptyA,A3 ; get the address
 TST.B  (A3); Is queue empty?
 BEQ    @1; if not branch
 MOVE.B #$28,(A1); if so, reset TBE interrupt
 MOVE.L (SP),(SP); Delay
 BRA    TxIExitA ; and exit
@1 LEA  TxByteOutA,A3; get the address
 MOVE   (A3),D0  ; get index to next data byte
 LEA    TxQueueA,A2; point to queue
 MOVE   #4,D1    ; get data offset
 MOVE.B 0(A2,D0),0(A1,D1) ; write data to SCC
 MOVE.L (SP),(SP); Delay
 ADDQ   #1,D0    ; update index
 CMP    #$100,D0
 BNE    @2
 MOVE   #0,D0
@2 LEA  TxByteOutA,A3; get the address
 MOVE   D0,(A3)
 LEA    TxByteInA,A3 ; get the address
 MOVE   (A3),D1
 CMP    D0,D1    ; is TxQueue empty?
 BNE    TxIExitA ; if not exit
 LEA    TxQEmptyA,A3 ; get the address
 MOVE   #$FFFF,(A3); if empty set flag

TxIExitA
 MOVE   (SP)+,SR ; enable interrupts
 RTS    ; and return
 

;PROCEDURE ResetSCCA;
; If you called InitSCCA at the beginning of your 
; application this routine must be called when 
; the application quits or the system will
; crash due to the interrupt handling pointers 
; becoming invalid.
ResetSCCA
 MOVEM.LD0/A0-A1,-(SP)  ; save registers
 MOVE   SR,-(SP) ; Save interrupts
 ORI    #$0300,SR; Disable interrupts
 
 MOVE.L SCCWr,A0 ; Get base Write address
 ADD    #aCtl,A0 ; Add offset for control
 MOVE.B #9,(A0)  ; pointer for SCC reg 9
 MOVE.L (SP),(SP); Delay
 MOVE.B #%10000000,(A0) ; Reset channel
 MOVE.L (SP),(SP); Delay
 BSR    ResetSCCChan ; branch to common reset routine

 MOVE.L #Lvl2DT,A0 ; dispatch table pointer
 MOVE   #RxIntOffsetA,D0  ; get offset to Rx vector
 LEA    PRxIntHandA,A1  ; point to previous vector stor
 MOVE.L (A1),0(A0,D0); restore previous int vector
 MOVE   #TxIntOffsetA,D0  ; get offset to Tx vector
 LEA    PTxIntHandA,A1    ; set Rx vector
 MOVE.L (A1),0(A0,D0); restore previous int vector
 MOVE   (SP)+,SR ; Restore interrupts
 MOVEM.L(SP)+,D0/A0-A1  ; restore registers
 RTS    ; and return

; This is the common reset routine for both channels
ResetSCCChan
 MOVE.B #15,(A0) ; pointer for SCC reg 15
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00001000,(A0) ; Enable DCD int
 MOVE.L (SP),(SP); Delay
 MOVE.B #0,(A0)  ; pointer for SCC reg 0
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00010000,(A0) ; Reset EXT/STATUS
 MOVE.L (SP),(SP); Delay
 MOVE.B #0,(A0)  ; pointer for SCC reg 0
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00010000,(A0) ; Reset EXT/STATUS again
 MOVE.L (SP),(SP); Delay
 MOVE.B #1,(A0)  ; pointer for SCC reg 1
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00000001,(A0) ; Enable mouse interrupts
 MOVE.L (SP),(SP); Delay
 MOVE.B #9,(A0)  ; pointer for SCC reg 9
 MOVE.L (SP),(SP); Delay
 MOVE.B #%00001010,(A0) ; Set master int enable
 MOVE.L (SP),(SP); Delay
 RTS

TxQueueADCB.B  $100,0; this is the queue
TxQEmptyA DC0    ; the queue empty flag
TxByteInA DC0    ; index to next cell in
TxByteOutADC0    ; index to next cell out
RxQueueADCB.B  $400,0; this is the queue
RxQEmptyA DC0    ; the queue empty flag
RxByteInA DC0    ; index to next cell in
RxByteOutADC0    ; index to next cell out
PRxIntHandA DC.L 0 ; Previous interrupt vector
PTxIntHandA DC.L 0 ; Previous interrupt vector

; These are the routines for the Printer Port
;PROCEDURE InitSCCB;
; Call this routine at the beginning of your 
; application if you are going
; to be using the printer port for MIDI 
; information transfers.
InitSCCB

 MOVE   SR,-(SP) ; Save interrupts
 MOVEM.LD0/A0-A2,-(SP)  ; Save registers
 ORI    #$0300,SR; Disable interrupts
 MOVE.L SCCRd,A1 ; Get base Read address
 ADD    #bCtl,A1 ; Add offset for control
 MOVE.B (A1),D0  ; Dummy read
 MOVE.L (SP),(SP); Delay
 MOVE.L SCCWr,A0 ; Get base Write address
 ADD    #bCtl,A0 ; Add offset for control
 MOVE.B #9,(A0)  ; pointer for SCC reg 9
 MOVE.L (SP),(SP); Delay
 MOVE.B #%01000000,(A0) ; Reset channel
 MOVE.L (SP),(SP); Delay
 BSR    InitSCCChan; branch to common init routine
 
; set up the interrupt vectors

 MOVE.L #Lvl2DT,A0 ; get dispatch table pointer
 MOVE   #RxIntOffsetB,D0  ; get offset to Rx vector
 LEA    PRxIntHandB,A1  ; point to previous vector stor
 MOVE.L 0(A0,D0),(A1); save previous int vector
 LEA    RxIntHandB,A1; set Rx vector
 MOVE.L A1,0(A0,D0)
 MOVE   #TxIntOffsetB,D0  ; get offset to Tx vector
 LEA    PTxIntHandB,A1    ; set Rx vector
 MOVE.L 0(A0,D0),(A1); save previous int vector
 LEA    TxIntHandB,A1; set Tx vector
 MOVE.L A1,0(A0,D0)
 MOVE   #SpecRecCondB,D0  ; offset to Special vector
 LEA    StubB,A1
 MOVE.L A1,0(A0,D0)
 
; initialize the flags & pointers
 LEA    RxByteInB,A2 ; get the address
 CLR    (A2)
 LEA    RxByteOutB,A2; get the address
 CLR    (A2)
 LEA    RxQEmptyB,A2 ; get the address
 MOVE #$FFFF,(A2)
 LEA    TxByteInB,A2 ; get the address
 CLR    (A2)
 LEA    TxByteOutB,A2; get the address
 CLR    (A2)
 LEA    TxQEmptyB,A2 ; get the address
 MOVE #$FFFF,(A2)
 MOVEM.L(SP)+,D0/A0-A2  ; Restore registers
 MOVE (SP)+,SR   ; Restore interrupts
 RTS    ; and return

;PROCEDURE TxMIDIB (TheData : integer);
; This is the routine to transmit a MIDI byte of 
; data through the Printer Port.
; To use this routine place the byte to be transmitted 
; as the lower 8 bits
; of a word on the stack, then call TxMIDIB.
TxMIDIB
 LINK   A6,#0    ; set frame pointer
 MOVE   SR,-(SP) ; Save interrupts
 MOVEM.LD0/A0-A3,-(SP)  ; Save registers
 ORI    #$0300,SR; Disable interrupts
 LEA    TxQEmptyB,A3 ; get the address
 TST.B  (A3); is TxQueue empty?
 BNE    TxQEB    ; if so branch
 LEA    TxByteInB,A3 ; get the address
 MOVE   (A3),D0  ; if not add byte to queue
 LEA    TxQueueB,A2; point to queue
 MOVE.B 9(A6),0(A2,D0)  ; place byte in queue
 ADDQ   #1,D0    ; update TxByteIn
 CMP    #$100,D0
 BNE    @1
 MOVE   #0,D0
@1 MOVE D0,(A3)
 BRA    TxExitB  ; and exit
TxQEB 
 MOVE.L SCCRd,A0 ; get SCC Read Address
 MOVE.L SCCWr,A1 ; get SCC Write address
 MOVE   #bCtl,D0 ; get index for Ctl
 BTST.B #TBE,0(A0,D0); transmit buffer empty?
 BNE    FirstByteB ; if so branch
 LEA    TxByteInB,A3 ; get the address
 MOVE   (A3),D0  ; if not add to queue
 LEA    TxQueueB,A2; point to queue
 MOVE.B 9(A6),0(A2,D0)  ; place byte in queue
 ADDQ   #1,D0    ; update pointer
 CMP    #$100,D0
 BNE    @1
 MOVE   #0,D0
@1 MOVE D0,(A3)
 LEA    TxQEmptyB,A3 ; get the address
 MOVE   #0,(A3)  ; reset queue empty flag
 BRA    TxExitB  ; and exit
FirstByteB
 MOVE   #bData,D0; get index to data
 MOVE.L (SP),(SP); delay
 MOVE.B 9(A6),0(A1,D0)  ; write data to SCC
 MOVE.L (SP),(SP); Delay
TxExitB 
 MOVEM.L(SP)+,D0/A0-A3  ; Restore registers
 MOVE   (SP)+,SR ; Restore interrupts
 UNLK   A6; release frame pointer
 MOVE.L (SP)+,A1 ; save return address
 ADD.L  #2,SP    ; move past data word
 MOVE.L A1,-(SP) ; put address back on stack
 RTS    ; and return

;FUNCTION RxMIDIB : LongInt;
; This routine gets a byte through the printer port. 
; To use this routine treat it like a Pascal function.  
; Leave space on the stack for a longword
; of data before calling this routine.  If the 
; data on the stack after
; the routine executes is 0 there was no MIDI 
; data available.  If it’s non-0
; the upper 3 bytes contain the counter value, 
; the MIDI byte is the low byte.
RxMIDIB
 LINK   A6,#0    ; set frame pointer
 MOVE   SR,-(SP) ; Save interrupts
 MOVEM.LD0-D1/A0-A3,-(SP) ; Save registers
 ORI    #$0300,SR; disable interrupts
 LEA    RxQEmptyB,A3 ; get the address
 TST.B  (A3); any data available?
 BEQ    @1; if so, branch
 MOVE.L #0,8(A6) ; if not, return with 0
 BRA    RxExitB
@1 LEA  RxByteOutB,A3; get the address
 MOVE   (A3),D0  ; get index to byte out
 LEA    RxQueueB,A2; point to queue
 MOVE.L #0,D1    ; clear data register
 MOVE.L 0(A2,D0),D1; get MIDI data
 MOVE.L D1,8(A6) ; place it on stack for return 
 ADDQ   #4,D0    ; update index
 CMP    #$400,D0
 BNE    @2
 MOVE   #0,D0
@2 LEA  RxByteOutB,A3; get the address
 MOVE   D0,(A3)
 LEA    RxByteInB,A3 ; get the address
 MOVE   (A3),D1
 CMP    D0,D1    ; is queue empty?
 BNE    RxExitB  ; if not exit
 LEA    RxQEmptyB,A3 ; get the address
 MOVE   #$FFFF,(A3); if empty, set flag
RxExitB 
 MOVEM.L(SP)+,D0-D1/A0-A3 ; Restore registers
 MOVE   (SP)+,SR ; restore interrupts
 UNLK   A6
 RTS    ; and return
 
; This is the interrupt routine for receiving through 
; the printer port. 
; It places the counter value and the MIDI byte in a 
; circular queue to be
; accessed later by the application.
; When the system gets this far, A0 contains the 
; SCC base read Ctl address
; and A1 contains the SCC base write Ctl address 
; for this channel.
; The data addresses are offset by 4 from the 
; control addresses.
; D0-D3/A0-A3 are already preserved, so they 
; may be used freely.
RxIntHandB
 MOVE   SR,-(SP)
 ORI    #$0300,SR; disable interrupts
@3 MOVE #4,D0    ; get data offset
 CLR.L  D1; prepare for data
 MOVE.L (SP),(SP); Delay
 MOVE.B 0(A0,D0),D1; read data from SCC
 MOVE.L (SP),(SP); Delay
 LEA    RxQueueB,A2; point to queue
 LEA    RxByteInB,A3 ; get the address
 MOVE   (A3),D0  ; get offset to next cell
 LEA    Counter,A3 ; get the address
 MOVE.L (A3),D2  ; put counter value in D2
 LSL.L  #8,D2    ; shift counter one byte
 ADD.L  D2,D1    ; combine counter and data
 MOVE.L D1,0(A2,D0); put longword in queue
 LEA    RxQEmptyB,A3 ; get the address
 MOVE   #0,(A3)  ; reset queue empty flag
 ADDQ   #4,D0    ; update index
 CMP    #$400,D0
 BNE    @1
 MOVE   #0,D0
@1 LEA  RxByteInB,A3 ; get the address
 MOVE   D0,(A3)
@2 BTST.B #0,(A0); is there more data?
 BNE    @3; do it again if there is
 MOVE   (SP)+,SR ; enable interrupts
 RTS    ; and return
 
; This is the interrupt routine for transmitting 
; a byte through the printer port.
; It checks to see if there is any data to send, 
; and if there is it sends it to 
; the SCC.  If there isn’t it resets the TBE 
; interrupt in the SCC and exits.
; When the system gets this far, A0 contains 
; the SCC base read Ctl address
; and A1 contains the SCC base write Ctl address 
; for this channel.
; The data addresses are offset by 4 from the 
; control addresses.
; D0-D3/A0-A3 are already preserved, so they may 
; be used freely.
TxIntHandB
 MOVE   SR,-(SP)
 ORI    #$0300,SR; disable interrupts
 LEA    TxQEmptyB,A3 ; get the address
 TST.B  (A3); Is queue empty?
 BEQ    @1; if not branch
 MOVE.B #$28,(A1); if so, reset TBE interrupt
 MOVE.L (SP),(SP); Delay
 BRA    TxIExitB ; and exit
@1 LEA  TxByteOutB,A3; get the address
 MOVE   (A3),D0  ; get index to next data byte
 LEA    TxQueueB,A2; point to queue
 MOVE   #4,D1    ; get data offset
 MOVE.B 0(A2,D0),0(A1,D1) ; write data to SCC
 MOVE.L (SP),(SP); Delay
 ADDQ   #1,D0    ; update index
 CMP    #$100,D0
 BNE    @2
 MOVE   #0,D0
@2 LEA  TxByteOutB,A3; get the address
 MOVE   D0,(A3)
 LEA    TxByteInB,A3 ; get the address
 MOVE   (A3),D1
 CMP    D0,D1    ; is TxQueue empty?
 BNE    TxIExitB ; if not exit
 LEA    TxQEmptyB,A3 ; get the address
 MOVE   #$FFFF,(A3); if empty set flag
TxIExitB
 MOVE   (SP)+,SR ; enable interrupts
 RTS    ; and return
 
;PROCEDURE ResetSCCB;
; If you called InitSCCB at the beginning of your 
; application this
; routine must be called when the application 
; quits or the system will
; crash due to the interrupt handling pointers 
; becoming invalid.
ResetSCCB
 MOVEM.LD0/A0-A1,-(SP)  ; save registers
 MOVE   SR,-(SP) ; Save interrupts
 ORI    #$0300,SR; Disable interrupts
 MOVE.L SCCWr,A0 ; Get base Write address
 ADD    #bCtl,A0 ; Add offset for control
 MOVE.B #9,(A0)  ; pointer for SCC reg 9
 MOVE.L (SP),(SP); Delay
 MOVE.B #%01000000,(A0) ; Reset channel
 MOVE.L (SP),(SP); Delay
 BSR    ResetSCCChan ; branch to common reset routine
 MOVE.L #Lvl2DT,A0 ; get dispatch table pointer
 MOVE   #RxIntOffsetB,D0  ; get offset to Rx vector
 LEA    PRxIntHandB,A1  ; point to previous vector stor
 MOVE.L (A1),0(A0,D0); restore previous int vector
 MOVE   #TxIntOffsetB,D0  ; get offset to Tx vector
 LEA    PTxIntHandB,A1    ; set Rx vector
 MOVE.L (A1),0(A0,D0); restore previous int vector
 MOVE   (SP)+,SR ; Restore interrupts
 MOVEM.L(SP)+,D0/A0-A1  ; restore registers
 RTS    ; and return

TxQueueBDCB.B  $100,0; this is the queue
TxQEmptyB DC0    ; the queue empty flag
TxByteInB DC0    ; index to next cell in
TxByteOutBDC0    ; index to next cell out
RxQueueBDCB.B  $400,0; this is the queue
RxQEmptyB DC0    ; the queue empty flag
RxByteInB DC0    ; index to next cell in
RxByteOutBDC0    ; index to next cell out
PRxIntHandB DC.L 0 ; Previous interrupt vector
PTxIntHandB DC.L 0 ; Previous interrupt vector

; This is the space for a special condition interrupt
; routine. All I do here is reset the error flag in the SCC
; and return. When the system gets this far, A0 contains 
; the SCC base read Ctl address
; and A1 contains the SCC base write Ctl address 
; for this channel.
; The data addresses are offset by 4 from the control
; addresses. D0-D3/A0-A3 are already preserved, so 
; they may be used freely.
StubA
 ORI    #$0300,SR; Disable interrupts
 MOVE.B #%00110000,(A1) ; Reset Error
 MOVE.L (SP),(SP); Delay
 ANDI   #$F8FF,SR; Restore interrupts
 RTS
; This is the space for a special condition interrupt
; routine.All I do here is reset the error flag in
; the SCC and return. When the system gets this far, 
; A0 contains the SCC base read Ctl address
; and A1 contains the SCC base write Ctl address 
; for this channel.
; The data addresses are offset by 4 from the 
; control addresses.
; D0-D3/A0-A3 are already preserved, so they may be 
; used freely.
StubB 
 ORI    #$0300,SR; Disable interrupts
 MOVE.B #%00110000,(A1) ; Reset Error
 MOVE.L (SP),(SP); Delay
 ANDI   #$F8FF,SR; Restore interrupts
 RTS

; These are the routines for the counter you can use for 
; time-stamping the incoming MIDI data.  This is useful 
; for writing sequencer type applications.
; The time-stamping is done on an interrupt level, 
; is extremely accurate,
; and uses the VIA timer #1.  This means that you can’t 
; use any of the Sound Manager routines because they use 
; timer #1 too.  If you want to create a metronome click 
; you have to write your own code that accesses
; the sound hardware directly without using timer #1.
; InitTimer and LoadTimer expect a word on the stack 
; to load the timer.
; To increment the counter every millisecond, load the 
; timer with decimal 782. If you aren’t going to use 
; time-stamping you can ignore these routines.
;PROCEDURE InitTimer (TimrValue : integer);
; Only call InitTimer once at the beginning 
; of your application 1 millisecond is decimal 782.
InitTimer
 LINK   A6,#0    ; set frame pointer
 MOVEM.LD0/A0-A1,-(SP)
 MOVE.L #Lvl1DT,A0 ; Point to level 1 dispatch table
 LEA    PrevIVC,A1 ; point to interrupt vector storage
 MOVE.L 24(A0),(A1); save previous interrupt vector
 LEA    CounterIntHand,A1 ;point to new interrupt handler
 MOVE.L A1,24(A0); put it in the dispatch table
 MOVE.L VIA,A1   ; point to the 6522 chip
 ORI.B  #$40,vACR(A1); set the timer to freerun mode
 MOVE.B #$C0,vIER(A1); Enable timer interrupts
 MOVE   8(A6),D0 ; Get timer value
 MOVE.B D0,vT1L(A1); set timer lo byte
 LSR    #8,D0    ; shift to hi byte
 MOVE.B D0,vT1CH(A1) ; set timer hi byte
 MOVEM.L(SP)+,D0/A0-A1
 UNLK   A6
 MOVE.L (SP)+,A0 ; save return address
 ADDQ   #2,SP    ; move past timer value
 MOVE.L A0,-(SP) ; replace return address
 RTS

;PROCEDURE LoadTimer (TimrValue : integer);
; Call LoadTimer whenever you want to change the timer value.
; 1 millisecond is decimal 782.
LoadTimer
 LINK   A6,#0    ; set frame pointer
 MOVEM.LD0/A0-A1,-(SP)
 MOVE.L VIA,A1   ; point to the 6522 chip
 MOVE   8(A6),D0 ; Get timer value
 MOVE.B D0,vT1L(A1); set timer lo byte
 LSR    #8,D0    ; shift to hi byte
 MOVE.B D0,vT1CH(A1) ; set timer hi byte
 MOVEM.L(SP)+,D0/A0-A1
 UNLK   A6
 MOVE.L (SP)+,A0 ; save return address
 ADDQ   #2,SP    ; move past timer value
 MOVE.L A0,-(SP) ; replace return address
 RTS

;PROCEDURE StartCounter;
; StartCounter sets the counter value to 1
StartCounter
 LEA    Counter,A0 ; point to the counter
 MOVE.L #1,(A0)  ; set it to 1
 RTS
;FUNCTION GetCounter : LongInt;
; GetCounter returns a longword that is the value 
; of the counter 
GetCounter
 MOVE.L A0,-(SP)
 LEA    Counter,A0 ; point to the counter
 MOVE.L (A0),8(SP) ; return it as function result
 MOVE.L (SP)+,A0
 RTS
;PROCEDURE QuitTimer;
; Call QuitTimer when your application is done or the system will crash.
QuitTimer
 MOVEM.LA0-A1,-(SP)
 MOVE.L VIA,A1   ; Disable 6522 interrupts
 MOVE.B #$40,vIER(A1)
 LEA    PrevIVC,A1 ; Restore previous interrupt vector
 MOVE.L #Lvl1dt,A0
 MOVE.L (A1),24(A0)
 MOVEM.L(SP)+,A0-A1
 RTS
; This is the interrupt handler routine for the counter. 
; When the system gets this far A1 contains the base 
; address of the VIA.
; It also preserves D0-D3/A0-A3.
CounterIntHand
 LEA    Counter,A0 ; point to the counter
 ADDQ.L #1,(A0)  ; Increment it
 MOVE.B vT1C(A1),D0; Clear interrupt flag on 6522
 RTS
Counter DC.L1  ; The counter
PrevIVC DC.L0  ; Previous interrupt vector
 END
 
AAPL
$524.94
Apple Inc.
+5.93
MSFT
$40.01
Microsoft Corpora
-0.39
GOOG
$536.10
Google Inc.
-20.44

MacTech Search:
Community Search:

Software Updates via MacUpdate

Tweetbot 1.5.1 - Popular iOS twitter cli...
Tweetbot is a full-featured OS X Twitter client with a lot of personality. Whether it's the meticulously-crafted interface, sounds and animation, or features like multiple timelines and column views... Read more
Mac DVDRipper Pro 4.1.7 - Copy, backup,...
Mac DVDRipper Pro is the DVD backup solution that lets you protect your DVDs from scratches, save your batteries by reading your movies from your hard disk, manage your collection with just a few... Read more
PDFpenPro 6.2 - Advanced PDF toolkit for...
PDFpenPro allows users to edit PDF's easily. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Create... Read more
PDFpen 6.2 - Edit and annotate PDFs with...
PDFpen allows users to easily edit PDF's. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Features... Read more
Monolingual 1.5.9 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
Starcraft II: Wings of Liberty 1.1.1.180...
Download the patch by launching the Starcraft II game and downloading it through the Battle.net connection within the app. Starcraft II: Wings of Liberty is a strategy game played in real-time. You... Read more
Sibelius 7.5.0 - Music notation solution...
Sibelius is the world's best-selling music notation software for Mac. It is as intuitive to use as a pen, yet so powerful that it does most things in less than the blink of an eye. The demo includes... Read more
Typinator 5.9 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
MYStuff Pro 2.0.16 - Create inventories...
MYStuff Pro is the most flexible way to create detail-rich inventories for your home or small business. Add items to MYStuff by dragging and dropping existing information, uploading new images, or... Read more

Latest Forum Discussions

See All

Have a Special Dead Trigger 2 Easter Bas...
Have a Special Dead Trigger 2 Easter Basket Full of Goodies, Courtesy of Madfinger Games Posted by Rob Rich on April 18th, 2014 [ permalink ] Dead Trigger 2 | Read more »
Zynga Launches Brand New Farmville Exper...
Zynga Launches Brand New Farmville Experience with Farmville 2: Country Escape Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
David. Review
David. Review By Cata Modorcea on April 18th, 2014 Our Rating: :: MINIMALISTIC IN A DIFFERENT WAYUniversal App - Designed for iPhone and iPad David is a minimalistic game wrapped inside of a soothing atmosphere in which the hero... | Read more »
Eyefi Unveils New Eyefi Cloud Service Th...
Eyefi Unveils New Eyefi Cloud Service That Allows Users to Share Media Across Personal Devices Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
Tales from the Dragon Mountain: The Lair...
Tales from the Dragon Mountain: The Lair Review By Jennifer Allen on April 18th, 2014 Our Rating: :: STEADY ADVENTURINGiPad Only App - Designed for the iPad Treading a safe path, Tales from the Dragon Mountain: The Lair is a... | Read more »
Yahoo Updates Flickr App with Advanced E...
Yahoo Updates Flickr App with Advanced Editing Features and More Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
My Incredible Body - A Kid's App to...
My Incredible Body - A Kid's App to Learn about the Human Body 1.1.00 Device: iOS Universal Category: Education Price: $2.99, Version: 1.1.00 (iTunes) Description: Wouldn’t it be cool to look inside yourself and see what was going on... | Read more »
Trials Frontier Review
Trials Frontier Review By Carter Dotson on April 18th, 2014 Our Rating: :: A ROUGH LANDINGUniversal App - Designed for iPhone and iPad Trials Frontier finally brings the famed stunt racing franchise to mobile, but how much does its... | Read more »
Evernote Business Notebook by Moleskin I...
Evernote Business Notebook by Moleskin Introduced – Support Available in Evernote for iOS Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
Sparkle Unleashed Review
Sparkle Unleashed Review By Jennifer Allen on April 18th, 2014 Our Rating: :: CLASSY MARBLE FLINGINGUniversal App - Designed for iPhone and iPad It’s a concept we’ve seen before, but Sparkle Unleashed is a solidly enjoyable orb... | Read more »

Price Scanner via MacPrices.net

iMacs on sale for up to $160 off MSRP this we...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
iPad Airs on sale this weekend for up to $100...
Best Buy has WiFi iPad Airs on sale for $50 off MSRP and WiFi + Cellular iPad Airs on sale for $100 off MSRP on their online store for a limited time, with prices now starting at $449. Choose free... Read more
Apple restocks refurbished Mac minis starting...
The Apple Store has restocked Apple Certified Refurbished Mac minis for up to $150 off the cost of new models. Apple’s one-year warranty is included with each mini, and shipping is free: - 2.5GHz Mac... Read more
Hyundai Brings Apple CarPlay To The 2015 Sona...
Hyundai Motor America has announced it will bring Apple CarPlay functionality to the 2015 Sonata. CarPlay is pitched as a smarter, safer and easier way to use iPhone in the car and gives iPhone users... Read more
Updated iPads Coming Sooner Than We Had Thoug...
MacRumors, cites KGI securities analyst Ming Chi Kuo, well-respected as an Apple product prognisticator, saying that Apple will introduce an upgraded iPad Air and iPad mini in 2014/Q3, meaning the... Read more
Toshiba Unveils New High And Low End Laptop M...
Toshiba has announced new laptop models covering both the high-end and low-end of the notebook computer spectrum. Toshiba 4K Ultra HD Laptop Toshiba’s new Satellite P55t features one of the world’s... Read more
Save up to $270 with Apple refurbished 13-inc...
The Apple Store has Apple Certified Refurbished October 2013 13″ Retina MacBook Pros available starting at $1099, with models up to $270 off MSRP. Apple’s one-year warranty is standard, and shipping... Read more
Apple now offering refurbished iPad mini with...
The Apple Store has Certified Refurbished 2nd generation iPad minis with Retina Displays now available starting at $339. Apple’s one-year warranty is included with each model, and shipping is free.... Read more
Microsoft Blinks – Drops Microsoft Office 365...
Microsoft has dropped the annual subscription fee for Microsoft Office 365 Personal – which is needed in order to create and edit documents in Microsoft Office for iPad. However, Apple’s iOS and OS X... Read more
New AVG Vault Apps for iOS and Android Help K...
AVG Technologies N.V. an online security company for 177 million active users, has announced the launch of its latest mobile application, AVG Vault. The free app introduces an innovative user... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Retail - Manager - Holyoke - Apple I...
Job Summary Keeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you’re a master of them all. In the store’s fast-paced, Read more
*Apple* Retail - Manager - Apple (United Sta...
Job SummaryKeeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you're a master of them all. In the store's fast-paced, dynamic Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Retail - Market Leader - Cincinnati...
…challenges of developing individuals, building teams, and affecting growth across Apple Stores. You demonstrate successful leadership ability - focusing on excellence Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.