	 PAGE	50,132	
	 TITLE	MIDI Interface Device Driver
	 %OUT	MIDI Interface Device Driver
	 ;***************************************************************
	 ;*			     MPU401				*
	 ;*								*
	 ;* This program is loaded as an I/O driver during MSDOS initi-	*
	 ;* alization. It contains a driver for a MIDI (Music Instru-	*
	 ;* ment Digital Interface) interface, the MPU401 from Roland	*
	 ;* Corp. This software has been tsted using an MPU401 with	*
	 ;* software revision level 1.5A.				*
	 ;*								*
	 ;* This driver acknowledges only three function requests from	*
	 ;* DOS, namely INIT, CHIN and CHOUT. INIT is for usage by DOS	*
	 ;* during system start-up. CHOUT receives control data setting	*
	 ;* the software interrupt number. The CHIN request passes an	*
	 ;* infinite stream of characters denoting the current software	*
	 ;* interrupt number. Consequently, an application program can	*
	 ;* set and read what software interrupt is used.		*
	 ;*								*
	 ;* Software interrupts lower than 35H are not allowed, because	*
	 ;* they may conflict with values used by MSDOS. Higher values	*
	 ;* may also conflict with DOS, but for these it is more unli-	*
	 ;* kely. Eventually you will need to find an own soft inter-	*
	 ;* rupt number. The default (68H) works nicely on the system	*
	 ;* where the driver was developed. You can set the software	*
	 ;* interrupt level by editing your CONFIG.SYS file.		*
	 ;*								*
	 ;* All access to the driver from a user program (except set-	*
	 ;* ting/reading the software interrupt number) is done using	*
	 ;* software interrupts, since then you don't have to pass	*
	 ;* through DOS's dispatcher etc. Software interrupt is much	*
	 ;* faster.							*
	 ;*								*
	 ;* When the application program issues a software interrupt,	*
	 ;* register AX should contain the desired 'function code',	*
	 ;* similar to a DOS INT21 call. Note that AX, not AH is used.	*
	 ;* The following 'function codes' are defined:			*
	 ;*								*
	 ;* 0	SINIT	Driver initialize				*
	 ;* 1	SFINI	Driver finitialize				*
	 ;* 2	SSTAT	Get driver status				*
	 ;* 3	SRERR	Reset error flags				*
	 ;* 4	SSTIM	Set timer					*
	 ;* 5	SRTIM	Read timer					*
	 ;* 6	SFBUF	Flush input data buffer				*
	 ;* 7	SRBUF	Read input data buffer				*
	 ;* 8	STBUF	Test input data buffer				*
	 ;* 9	SDXBF	Define exclusive message buffer			*
	 ;* 10  SFXBF	Flush exclusive message buffer			*
	 ;* 11	SRXBF	Read exclusive message buffer			*
	 ;* 12	STXBF	Test exclusive message buffer			*
	 ;* 13	SOCMD	Send MPU command				*
	 ;* 14	SODAT	Send MPU data					*
	 ;*								*
	 ;* Some software interrupts return with a completion code in	*
	 ;* register DX and/or a return value in AX. (DX-AX are the	*
	 ;* registers for a 'long' return value used by most 'C' com-	*
	 ;* pilers - MicroSoft, Wizard, DeSmet, and (yech!) Digital	*
	 ;* Research. Lattice uses BX-AX).				*
	 ;*								*
	 ;* Data coming from the MPU401 is by default buffered in an	*
	 ;* internal buffer ('DBUF') which has a default size of 1 kb.	*
	 ;* At driver initialization, another size may be selected for	*
	 ;* this buffer. An application program may also allocate it's	*
	 ;* own data buffer, which will then be used by the driver in-	*
	 ;* stead of the internal one. MIDI exclusive messages are not	*
	 ;* buffered by default - they are just ignored. An applica-	*
	 ;* tion may set up an own buffer for these.			*
	 ;*								*
	 ;* The driver responds to certain parameters in the invoca-	*
	 ;* tion line in CONFIG.SYS:					*
	 ;*								*
	 ;* DEVICE=MPU401.DEV /B:nnnn /H:n /P:nnnn /S:nn		*
	 ;*								*
	 ;* /B:nnnn	Set Internal Buffer Size to nnnn (200H <= n	*
	 ;*		<= 8000H)					*
	 ;* /H:n	Set Hardware Interrupt to n (0-0FH for an AT,	*
	 ;*		0-7 for an XT).					*
	 ;* /P:nnnn	Set MPU401 Port Address to nnnn (0-3FFH).	*
	 ;* /S:nn	Set Software Interrupt number to nn (35H-0FFH).	*
	 ;*		(May also be changed by application.)		*
	 ;*								*
	 ;* (n, nn, and nnnn are given in hexadecimal notation.)	*
	 ;***************************************************************
	 ;*								*
	 ;* Original Author B. Larsson - All wrongs reserved.		*
	 ;*								*
	 ;* (Many ideas taken from the CMU MIDI Toolkit sources -	*
	 ;*  notably aintr.asm, mpu.c and cintr.c. Regards to the	*
	 ;*  authors of all those).					*
	 ;*								*
	 ;***************************************************************
	 ;* I herewith place this software is in the PUBLIC DOMAIN. You	*
	 ;* are free to make any modifications to it that you want. THE	*
	 ;* ONLY RESTRICTION is that you PLEASE leave the credit notes	*
	 ;* of all previous authors in the source. Please also maintain	*
	 ;* a proper change log, so that followers see what has been	*
	 ;* done, and WHY! Thank You!					*
	 ;***************************************************************
	 ;*								*
	 ;* 1.0:  Started:					870105	*
	 ;*	  Functional and debugged except exclusive		*
	 ;*	  soft ints:					870112	*
	 ;* 1.1:  Exclusive buffer implemented:			870117	*
	 ;* 1.2:  Does 5000 dummy byte reads from MPU401 at		*
	 ;*	  initialization before it gives up (did just		*
	 ;*	  200):						870118	*
	 ;* 1.30: Cute little sign-on string:			871109	*
	 ;***************************************************************
	 ;
CODSEG	 SEGMENT PUBLIC PARA 'CODE'
	 ASSUME	 CS:CODSEG,ES:NOTHING,DS:NOTHING
	 ;
	 SUBTTL	Equates
	 %OUT	Equates
	 PAGE
	 ;
	 ;***************************************************************
	 ;* Equates							*
	 ;***************************************************************
	 ;
	 ;* ASCII Equates.
	 ;
ASCICR	 EQU	0DH				;CARRIAGE RETURN
ASCILF	 EQU	0AH				;LINE FEED
UCMASK	 EQU	5FH				;MASKS LOWER CASE TO UPPER
	 ;
	 ;* System related equates.
	 ;
SYSTEM	 EQU	21H				;SYSTEM CALL
PRTSTR	 EQU	9				;PRINT STRING REQUEST
JFAROPC	 EQU	0EAH				;JUMP FAR OP-CODE
	 ;
	 ;* Device driver equates.
	 ;
DEVATR	 EQU	1000000000000000B		;DEVICE ATTRIBUTES
INITRQ	 EQU	0				;INIT FUNCTION REQUEST
CHINRQ	 EQU	4				;CHIN FUNCTION REQUEST
CHOUTRQ	 EQU	8				;CHOUT FUNCTION REQUEST
	 ;
RDYSTT	 EQU	0000000100000000B		;READY STATUS	 RETURN CODE
WRPSTT	 EQU	1000000000000000B		;WRITE PROTECT	 RETURN CODE
ILLSTT	 EQU	1000000000000011B		;BAD REQ STATUS	 RETURN CODE
	 ;
	 ;* I/O Request header offsets.
	 ;
CMDOFS	 EQU	2				;REQUEST CODE	 REQ HDR OFS
STTOFS	 EQU	3				;RETURN STATUS	 REQ HDR OFS
UNCOFS	 EQU	13				;RETURN UNITCODE REQ HDR OFS
EOFOFS	 EQU	14				;END ADDR OFFSET REQ HDR OFS
ESGOFS	 EQU	16				;END ADDR SEG	 REQ HDR OFS
PDOOFS	 EQU	14				;PARM WRD OFS	 REQ HDR OFS
PDSOFS	 EQU	16				;PARM WRD SEG	 REQ HDR OFS
BCTOFS	 EQU	18				;BYTE COUNT	 REQ HDR OFS
PMOOFS	 EQU	18				;ARGUMENT OFFS	 REQ HDR OFS
PMSOFS	 EQU	20				;ARGUMENT SEG	 REQ HDR OFS
	 ;
	 ;* Default values.
	 ;
DDATPORT EQU	330H				;DEFAULT DATA PORT LOCATION
DCTLPORT EQU	331H				;DEFAULT CTRL PORT LOCATION
PORTMAX	 EQU	7FEH				;MAX PORT LOCATION
DHRDINT	 EQU	2				;DEFAULT HARD INT TO USE
DSFTINT	 EQU	68H				;DEFAULT SOFT INT TO USE
MINSFTI	 EQU	35H				;MINIMUM SOFT INT TO USE
MAXSFTI	 EQU	0FFH				;MAXIMUM SOFT INT TO USE
	 ;
	 ;* MIDI port values.
	 ;
DSRBIT	 EQU	80H				;DSR BIT MASK
DRRBIT	 EQU	40H				;DRR BIT MASK
	 ;
	 ;* Buffer size values.
	 ;
MINBSIZ	 EQU	512				;MIN BUFFER SIZE
DIBSIZ	 EQU	1024				;DEFAULT 1K INTERNAL BUFFER
MAXBSIZ	 EQU	32768				;MAX BUFFER SIZE
	 ;
	 ;* Software interrupt completion codes.
	 ;
CPLOK	 EQU	0				;OK COMPLETION
CPLIFNC	 EQU	1				;ILLEGAL FUNCTION CODE
CPLPASS	 EQU	2				;DRIVER INACTIVE
CPLMOFL	 EQU	3				;MIDI PROVIDES TOO MUCH DATA
CPLPRTE	 EQU	4				;MIDI PROTOCOL ERROR
CPLBERR	 EQU	5				;INVALID BUFFER SPECIFICATION
CPLTERR	 EQU	6				;MIDI TIMEOUT ERROR
CPLNOXB	 EQU	7				;EXCLUSIVE BUFFER NOT DEFINED
	 ;
	 ;* MPU401 and MIDI command and status codes.
	 ;
MPURES	 EQU	0FFH				;MPU401 RESET COMMAND
MPUACK	 EQU	0FEH				;MPU401 'ACK' BYTE
MPUOFLO	 EQU	0F8H				;INDICATES MPU TIMER OVERFLOW
MPUNOOP	 EQU	0F8H				;NOOP MPU MARK CODE
MPUGRCT	 EQU	0ABH				;GET MPU RECORD COUNTER CMD
FAKEDAT	 EQU	0F8H				;FAKE DATA USED AT TIMEOUT
MPUMAXT	 EQU	0EFH				;MAX TIME BYTE FROM MPU401
MPUSBIT	 EQU	80H				;MASK FOR RUNNING STATUS BIT
MPUSMSK	 EQU	0F0H				;MASK FOR RUN STATUS NIB
MPUSMAX	 EQU	0EFH				;MAX RUN STATUS VALUE
ATCHMSK	 EQU	0E0H				;MASKS Cx AND Dx TO C0
ATOUCH	 EQU	0C0H				;AFTERTOUCH STATUS BYTE
MPUSYSM	 EQU	0FFH				;STATUS BYTE FOR SYSTEM MSG
MPUMIDX	 EQU	0F0H				;YTE FOR SYSTEM EXCLUSIVE MSG
	 ;
	 ;* Miscellaneous.
	 ;
EOICMD	 EQU	20H				;EOI COMMAND FOR 8259 CHIP
OFLTIM	 EQU	240				;MPU TIMEOUT VALUE
	 ;
	 SUBTTL	Static Data Area
	 %OUT	Static Data Area
	 PAGE
	 ;
	 ;***************************************************************
	 ;* Static storage						*
	 ;***************************************************************
	 ;
DVNXT:	 DW	0FFFFH				;PTR TO NEXT DRIVER HEADER
	 DW	0FFFFH
DVATR:	 DW	DEVATR				;DEVICE'S ATTRIBUTES
DVSTRA:	 DW	OFFSET STRAT			;ADDRESS OF STRATEGY ENTRY
DVEXEC:	 DW	OFFSET EXEC			;ADDRESS OF EXEC ('INT') ENTRY
DVNAME:	 DB	'MPU401$$'			;DEVICE NAME
	 ;
	 ;*  Buffer related values.
	 ;
IBSIZ:	 DW	0				;SIZE OF BUILT-IN BUFFER
BFADR:	 DD	0				;BUFFER ADDRESS
BFSIZ:	 DW	0				;SIZE OF CURRENT BUFFER
BFPUT:	 DW	0				;WRITE INDEX INTO BUFFER
BFPIC:	 DW	0				;READ INDEX INTO BUFFER
NENT:	 DW	0				;BYTES IN BUFFER
XBFADR:	 DD	0				;EXCL BUFFER ADDRESS
XBFSIZ:	 DW	0				;SIZE OF EXCLUSIVE BUFFER
XBFPUT:	 DW	0				;WRITE INDEX INTO EXCL BUFFER
XBFPIC:	 DW	0				;READ INDEX INTO EXCL BUFFER
XNENT:	 DW	0				;BYTES IN EXCL BUFER
	 ;
	 ;* Port addresses etc.
	 ;
DATPORT: DW	0				;MIDI DATA PORT ADDRESS
CTLPORT: DW	0				;MIDI CONTROL PORT ADDRESS
EOIPORT: DW	0				;PORT ADDRESS FOR EOI
ENAPORT: DW	0				;PORT ADDRESS FOR EN/DISABLE
	 ;
	 ;* Temporary storage locations.
	 ;
RHPTR:	 DD	0				;DWORD POINTER TO REQ HDR
TMPSP:	 DW	0				;INTERRUPTED PROGRAM'S SP
TMPSS:	 DW	0				;INTERRUPTED PROGRAM'S SS
	 ;
	 ;* Interrupt related values.
	 ;
HRDINT:	 DB	0				;HARDWARE INTERRUPT NUMBER
SFTINT:	 DB	0				;SOFTWARE INTERRUPT NUMBER
OSWVEC:	 DD	0				;ORIGINAL SW VECTOR
HWVECAD: DW	0				;HW VECTOR ADDRESS IN SEGM 0
ENAMASK: DB	0				;INT ENABLE MASK
DISMASK: DB	0				;INT DISABLE MASK
	 ;
	 ;* Miscellaneous flags etc.
	 ;
STATREC: DW	0				;STATUS RECORD
						;BIT 15 DRIVER ACTIVE
						;BIT 14 USED FOR REPORTING
						; AT/XT STATUS. NOT WRITTEN
						; IN THIS WORD.
						;BIT 10 MPU CMD ACK'ED
						;BIT 9 DATA IN EXCL BUFFER
						;BIT 8 DATA IN BUFFER
						;BIT 3 INPUT PROTOCOL ERROR
						;BIT 2 TIMEOUT ERROR
						;BIT 1 EXCL BUFFER OVERFLOW
						;BIT 0 BUFFER OVERFLOW
FNCTAB:	 DW	FNCTABI				;CURRENT FUNCTION JUMP TABLE
MPUDELY: DW	0				;MAX TIME TO WAIT FOR MPU401
ATFLAG:	 DB	0				;NON-ZERO IF PC-AT
ORGENA:	 DB	0				;ORIGINAL INT ENABLE MASK
LASTTIM: DB	0				;TIME OF LAST ERROR
LASTERR: DB	0				;BYTE OF LAST ERROR
MIDISTT: DB	0				;MIDI RUNNING STATUS BYTE
	 ;
	 ;* Real-time timer accumulator
	 ;
ACKTIME: DB	0				;FLAGS MPUACK AWAITS TIME BYTE
TIMER:	 DD	0				;32 BITS TIMER ACKUMULATOR
	 ;
	 ;* Function jump table for active driver.
	 ;
FNCTABA: DW	SINITA				;INITIALIZE
	 DW	SFINIA				;FINITIALIZE
	 DW	SSTAT				;GET DRIVER STATUS
	 DW	SRERR				;RESET ERROR FLAGS
	 DW	SSTIM				;SET TIMER
	 DW	SRTIMA				;READ TIMER
	 DW	SFBUF				;FLUSH INPUT BUFFER
	 DW	SRBUFA				;READ INPUT BUFFER
	 DW	STBUFA				;TEST INPUT BUFFER
	 DW	SDXBFA				;DEFINE EXCLUSIVE BUFFER
	 DW	SFXBF				;FLUSH EXCLUSIVE BUFFER
	 DW	SRXBFA				;READ EXCLUSIVE BUFFER
	 DW	STXBFA				;TEST EXCLUSIVE BUFFER
	 DW	SOCMDA				;SEND COMMAND
	 DW	SODATA				;SEND DATA
	 ;
	 ;* Function jump table for inactive driver.
	 ;
FNCTABI: DW	SINITI				;INITIALIZE
	 DW	SFINII				;FINITIALIZE
	 DW	SSTAT				;GET DRIVER STATUS
	 DW	SRERR				;RESET ERROR FLAGS
	 DW	SSTIM				;SET TIMER
	 DW	SRTIMI				;READ TIMER
	 DW	SFBUF				;FLUSH INPUT BUFFER
	 DW	SRBUFI				;READ INPUT BUFFER
	 DW	STBUFI				;TEST INPUT BUFFER
	 DW	SDXBFI				;DEFINE EXCLUSIVE BUFFER
	 DW	SFXBF				;FLUSH EXCLUSIVE BUFFER
	 DW	SRXBFI				;READ EXCLUSIVE BUFFER
	 DW	STXBFI				;TEST EXCLUSIVE BUFFER
	 DW	SOCMDI				;SEND COMMAND
	 DW	SODATI				;SEND DATA
	 ;
MAXFNC	 EQU	($-FNCTABI) / 2			;NUMBER OF TABLE ENTRIES
	 ;
REVCOD:	 DB	'MPU401 v.1.30 - 871109, '
	 DB	'Author Bjorn Larsson, Stockholm 1987 '
	 ;
STACK:	 DB	200 DUP ( 0 )			;OUR OWN STACK
STKTOP:	 DB	0				;TOP OF IT
	 ;
	 SUBTTL	Driver Entry Points
	 %OUT	Driver Entry Points
	 PAGE
	 ;
	 ;***************************************************************
	 ;* Strategy routine.						*
	 ;*								*
	 ;* Just stores away the request header pointer in RHPTR.	*
	 ;***************************************************************
	 ;
STRAT	 proc	far
	 ;
	 MOV	WORD PTR CS:RHPTR,BX		;SAVE OFFSET
	 MOV	WORD PTR CS:RHPTR+2,ES		;AND PARAGRAPH
	 RET
	 ;
STRAT	 endp
	 ;
	 ;***************************************************************
	 ;* 'Interrupt' routine.					*
	 ;*								*
	 ;* Performs the action specified at the call to the strategy	*
	 ;* routine.							*
	 ;***************************************************************
	 ;
EXEC	 proc	far
	 ;
	 PUSH	DS				;SAVE MACHINE STATE
	 PUSH	ES
	 PUSH	SI
	 PUSH	DI
	 PUSH	AX
	 PUSH	BX
	 PUSH	CX
	 PUSH	DX
	 ;
	 PUSH	CS				;MAKE DS = CS
	 POP	DS
	 ;
	 MOV	BX,WORD PTR RHPTR		;PICK UP POINTER TO REQ HDR
	 MOV	ES,WORD PTR RHPTR+2
	 MOV	AL,ES:[BX]+CMDOFS		;GET FUNCTION CODE
	 ;
	 CMP	AL,INITRQ			;INIT?
	 JNZ	EXEC1
	 ;
	 CALL	INIT				;DO INIT REQUEST
	 JMP	SHORT READY
	 ;
EXEC1:	 CMP	AL,CHINRQ			;CHIN?
	 JZ	CHIN
	 CMP	AL,CHOUTRQ			;CHOUT?
	 JZ	CHOUT
	 ;
	 NOP
	 NOP
	 ;
ILLREQ:	 MOV	ES:WORD PTR STTOFS[BX],ILLSTT	;SAY ILLEGAL REQUEST
	 JMP	SHORT EXIT
	 ;
READY:	 MOV	ES:WORD PTR STTOFS[BX],RDYSTT	;SAY OK
	 ;
EXIT:	 POP	DX				;RESTORE MACHINE STATE
	 POP	CX
	 POP	BX
	 POP	AX
	 POP	DI
	 POP	SI
	 POP	ES
	 POP	DS
	 RET
	 ;
	 ;***************************************************************
	 ;* CHIN (4)							*
	 ;*								*
	 ;* Always sends back the software interrupt number in the form	*
	 ;* of one character.						*
	 ;***************************************************************
	 ;
CHIN:	 MOV	DS,ES:WORD PTR PDSOFS[BX]	;GET SEGM OF DTA
	 MOV	DI,ES:WORD PTR PDOOFS[BX]	;AND OFFSET
	 MOV	AL,CS:BYTE PTR SFTINT		;GET SOFT INT NUMBER
	 MOV	BYTE PTR DS:[DI],AL		;AND RETURN IT AS A CHAR
	 JMP	READY				;SUCCESSFULL RETURN
	 ;
	 ;***************************************************************
	 ;* CHOUT (8)							*
	 ;*								*
	 ;* Installs the software interrupt number given by the charac-	*
	 ;* ter passed.							*
	 ;***************************************************************
	 ;
CHOUT:	 MOV	DS,ES:WORD PTR PDSOFS[BX]	;GET SEGM OF DTA
	 MOV	DI,ES:WORD PTR PDOOFS[BX]	;AND OFFSET
	 MOV	CL,BYTE PTR DS:[DI]		;GET SOFT INT NUMBER
	 ;
	 CMP	CL,MINSFTI			;CHECK VALID INT NUMBER
	 JAE	CHOUTOK				;MUST BE GREATER
	 ;
	 MOV	ES:WORD PTR STTOFS[BX],WRPSTT	;SAY WRITE PROTECT
	 JMP	EXIT				;ERROR RETURN
	 ;
	 ;* Valid new value. First restore old vector.
	 ;
CHOUTOK: XOR	AX,AX				;CLEAR DS
	 MOV	DS,AX
	 MOV	AL,CS:BYTE PTR SFTINT		;GET SOFT INT
	 ADD	AX,AX				;MULT BY 4
	 ADD	AX,AX
	 MOV	SI,AX
	 MOV	AX,CS:WORD PTR OSWVEC		;GET OLD VECTOR'S OFFSET
	 MOV	WORD PTR DS:[SI],AX		;RESTORE IT
	 MOV	AX,CS:WORD PTR OSWVEC+2		;GET SEGMENT
	 MOV	WORD PTR DS:2[SI],AX		;AND RESTORE
	 ;
	 ;* Now install the new value.
	 ;
	 MOV	CS:BYTE PTR SFTINT,CL		;INSTALL IT
	 XOR	CH,CH				;IN CX
	 ADD	CX,CX				;MULT BY 4
	 ADD	CX,CX
	 MOV	SI,CX
	 ;
	 MOV	AX,WORD PTR DS:[SI]		;GET OLD VECTOR'S OFFSET
	 MOV	CS:WORD PTR OSWVEC,AX		;SAVE IT
	 MOV	AX,WORD PTR DS:2[SI]		;GET SEGMENT
	 MOV	CS:WORD PTR OSWVEC+2,AX		;AND SAVE
	 ;
	 MOV	DS:WORD PTR [SI],OFFSET SWIHNL	;INSTALL SOFTWARE INTERRUPT
	 MOV	DS:WORD PTR 2[SI],CS		;HANDLER ADDRESS	
	 ;
	 JMP	READY				;SUCCESSFULL RETURN
	 ;
EXEC	 endp
	 ;
	 SUBTTL	Soft Interrupt Dispatcher
	 %OUT	Soft Interrupt Dispatcher
	 PAGE
	 ;
	 ;***************************************************************
	 ;* Software Interrupt Handler.					*
	 ;*								*
	 ;* You come here when an application program wants to do some-	*
	 ;* thing with the MPU401, and tries to use the software int.	*
	 ;*								*
	 ;* At entry, reg AX contains the Function Code, and any input	*
	 ;* parameter is passed in BX, CX and DX.			*
	 ;*								*
	 ;* The individal functions return any data in AX.		*
	 ;* Functions that can detect errors return an error code in	*
	 ;* DX. Functions that do not detecte errors (always success-	*
	 ;* full) do not set DX.					*
	 ;*								*
	 ;* Completion code may be					*
	 ;*								*
	 ;* 0:		OK, no error.					*
	 ;* 1:		Illegal Function Code.				*
	 ;* 2:		Driver not activated.				*
	 ;* 3:		MIDI data overflow error.			*
	 ;* 4:		MIDI protocol error.				*
	 ;* 5:		Illegal buffer specification.			*
	 ;* 6:		MIDI timeout error.				*
	 ;*								*
	 ;* This entry point will destroy the AX and DX register only.	*
	 ;***************************************************************
	 ;
SWIHNL	 proc	far
	 ;
	 STI					;RE-ENABLE INTERRUPTS
	 CMP	AX,MAXFNC			;VALID FUNCTION CODE?
	 JB	SWIHNL1				;JUMP IF SO
	 ;
	 MOV	DX,CPLIFNC			;ILLEGAL FUNCTION CODE
	 IRET
	 ;
SWIHNL1: PUSH	DI
	 PUSH	DS				;SAVE DS
	 PUSH	CS				;SET DS=CS
	 POP	DS
	 ADD	AX,AX				;SHIFT LEFT FOR WORD INDEX
	 MOV	DI,WORD PTR FNCTAB		;GET THE JUMP TABLE
	 ADD	DI,AX
	 JMP	WORD PTR [DI]			;GO TO HANDLER
	 ;
	 SUBTTL	Soft Interrupt Init
	 %OUT	Soft Interrupt Init
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 0:	SINIT:							*
	 ;*								*
	 ;* If the driver was currently active, it is set inactive.	*
	 ;*								*
	 ;* if BX is non-zero, DX:CX is the address of a non-default	*
	 ;* data buffer, and BX is the size of it. If BX is 0, the in-	*
	 ;* ternal buffer is used. Then some hardware initialization is	*
	 ;* done to the MPU401. Extensive checks are made that the	*
	 ;* MPU401 is present and responds correctly. If something does	*
	 ;* not work properly, the initialization is not performed and	*
	 ;* an error is reported. If all goes well, DX returns CPLOK.	*
	 ;***************************************************************
	 ;
	 ;* For active driver (must finitialize first).
	 ;
SINITA:	 CALL	FINITIT				;DO DRIVER FINITIALIZE
	 AND	DX,DX				;SEE IF OK?
	 JZ	SINITI				;THEN JUMP
	 ;
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET					;RETURN FROM FINITI'S ERROR
	 ;
	 ;* For inactive driver (normal initialization).
	 ;
SINITI:	 PUSH	CX
	 ;
	 ;* First initialize most values to defaults.
	 ;
	 MOV	WORD PTR BFADR,OFFSET DBUF	;SET UP TO USE INTERNAL BUFFER
	 MOV	WORD PTR BFADR+2,CS		;SEGMENT
	 MOV	AX,WORD PTR IBSIZ		;BUFFER SIZE
	 MOV	WORD PTR BFSIZ,AX
	 XOR	AX,AX				;CLEAR CS
	 MOV	WORD PTR BFPIC,AX		;CLEAR INDICES
	 MOV	WORD PTR BFPUT,AX
	 MOV	WORD PTR XBFPIC,AX
	 MOV	WORD PTR XBFPUT,AX
	 MOV	WORD PTR XBFSIZ,AX		;NO EXCL BUFFER
	 MOV	WORD PTR NENT,AX		;NO ENTRIES IN BUFFER YET
	 MOV	WORD PTR XNENT,AX		;NO EXCL ENTRIES
	 MOV	WORD PTR TIMER,AX		;CLEAR TIME RECORD
	 MOV	WORD PTR TIMER+2,AX
	 MOV	BYTE PTR ACKTIME,AL		;CLEAR TIME EXPECTATION FLAG
	 MOV	BYTE PTR MIDISTT,90H		;INITIAL MIDI RUNNING STATUS
	 ;
	 ;* Check if non-default buffer is to be used.
	 ;
	 AND	BX,BX				;CHECK IF NON-DEFAULT BUFFER?
	 JZ	SINIT4				;JUMP IF NOT
	 ;
	 CMP	BX,MINBSIZ			;ERROR IF SIZE < MIN
	 JB	SINIT1
	 ;
	 CMP	BX,MAXBSIZ			;ERROR IF SIZE > MAX
	 JBE	SINIT2				;JUMP IF OK
	 ;
SINIT1:	 MOV	DX,CPLBERR			;ERROR CODE
	 JMP	SINITX				;RETURN
	 ;
SINIT2:	 CMP	CX,10H				;MAKE OFFSET < 1 PARAGRAPH
	 JB	SINIT3
	 ;
	 SUB	CX,10H				;STEP OFFSET DOWN
	 INC	DX				;AND SEGMENT UP
	 JMP	SINIT2				;LOOP UNTIL DONE
	 ;
SINIT3:	 MOV	WORD PTR BFSIZ,BX		;INSTALL NEW VALUES
	 MOV	WORD PTR BFADR,CX		;ADDRESS OFFSET
	 MOV	WORD PTR BFADR+2,DX		;AND SEGMENT
	 ;
	 ;* Read any data there is in the MPU401 (max 5000 bytes).
	 ;
SINIT4:	 MOV	CX,5000				;MAX 5000 BYTES
	 ;
SINIT5:	 MOV	DX,WORD PTR CTLPORT		;STATUS PORT
	 IN	AL,DX				;GET STATUS
	 AND	AL,DSRBIT			;SEE IF DATA THERE?
	 JNZ	SINIT7				;JUMP IF NOT
	 ;
	 DEC	DX				;DATA PORT
	 IN	AL,DX				;DO A READ
	 INC	DX				;CTRL PORT
	 MOV	AL,200
SINIT6:	 DEC	AL				;SHORT DELAY LOOP
	 JNZ	SINIT6
	 ;
	 DEC	CX				;DOWN COUNT
	 JNZ	SINIT5				;LOOP IF NOT COUNTED OUT
	 ;
	 MOV	DX,CPLMOFL			;TO MUCH DATA ERROR CODE
	 JMP	SINITX
	 ;
	 ;* MPU401 buffer empty. Await MPU401 ready for command.
	 ;
SINIT7:	 MOV	CX,500				;TIMEOUT FOR DRR LOW
	 ;
SINIT8:	 IN	AL,DX				;WAIT FOR DRR LOW
	 AND	AL,DRRBIT			;CHECK IT
	 JZ	SINIT9
	 ;
	 DEC	CX				;COUNT DOWN
	 JNZ	SINIT8
	 ;
	 ;* Timeout, MPU401 never gets ready for command. Error return.
	 ;
	 JMP	SHORT SINIT12
	 ;
	 ;* MPU401 ready for command. Reset it to known initial state.
	 ;
SINIT9:	 MOV	AL,MPURES			;MIDI RESET CODE
	 OUT	DX,AL				;DO IT
	 ;
	 MOV	CX,WORD PTR MPUDELY		;TIMEOUT FOR RESPONSE
	 ;
SINIT10: IN	AL,DX				;GET STATUS
	 AND	AL,DSRBIT			;SEE IF DATA THERE?
	 JZ	SINIT11				;THEN JUMP
	 ;
	 DEC	CX				;TIME OUT YET?
	 JNZ	SINIT10				;TRY AGAIN IF NOT
	 ;
	 ;* Timeout, no acknowledge from MPU401. Error return.
	 ;
	 MOV	DX,CPLTERR			;TIMEOUT ERROR
	 JMP	SINITX
	 ;
	 ;* MPU401 replied with a byte. Check it's ACK.
	 ;
SINIT11: DEC	DX				;DATA PORT
	 IN	AL,DX
	 CMP	AL,MPUACK			;SHOULD BE 'ACKNOWLEDGE'
	 JZ	SINIT13				;JUMP IF OK
	 ;
SINIT12: MOV	DX,CPLPRTE			;PROTOCOL ERROR
	 JMP	SINITX
	 ;
	 ;* Coming here, the MPU401 is present, and responds correctly.
	 ;* Now initialize some more values.
	 ;
SINIT13: MOV	WORD PTR STATREC,8400H		;DRIVER ACTIVE, NO ERROR/DATA
	 MOV	AX,OFFSET FNCTABA		;ACTIVE DRIVER JUMP TABLE
	 MOV	WORD PTR FNCTAB,AX		;USE IT IN FUTURE
	 ;
	 ;* Save original hardware interrupt vector and replace it
	 ;* by our own.
	 ;
	 PUSH	DS
	 XOR	AX,AX				;CLEAR CS
	 MOV	DS,AX				;FOR ACCESS TO SEGM 0
	 MOV	DI,CS:WORD PTR HWVECAD		;VECTOR LOCATION
	 CLI					;SHUT OFF DOING THIS
	 MOV	AX,WORD PTR [DI]		;GET ORIGINAL VECTOR OFFS
	 MOV	CS:WORD PTR OHWVEC,AX		;SAVE IT
	 MOV	AX,WORD PTR 2[DI]		;GET SEGMENT
	 MOV	CS:WORD PTR OHWVEC+2,AX		;SAVE IT
	 MOV	WORD PTR [DI],OFFSET HWIHNL	;INSTALL OUR OFFSET
	 MOV	WORD PTR 2[DI],CS		;INSTALL OUR SEGMENT
	 STI					;ALLOW INT BACK ON
	 POP	DS				;RECOVER DS (=CS)
	 ;
	 ;* Give a (probably unnessecary) EOI to interrupt controller.
	 ;
	 MOV	DX,WORD PTR EOIPORT		;PORT TO COMMAND EOI TO
	 MOV	AL,EOICMD			;CLEAR IN-SERVICE FLIP-FLOP
	 OUT	DX,AL				;IN INT CONTROLLER
	 ;
	 ;* Enable hardware interrupts in 8259.
	 ;
	 MOV	DX,WORD PTR ENAPORT		;ENABLE MASK PORT
	 IN	AL,DX				;GET CURRENT SETTINGS
	 MOV	BYTE PTR ORGENA,AL		;KEEP ORIGINAL MASK
	 AND	AL,BYTE PTR ENAMASK		;CLEAR BIT TO ENABLE
	 OUT	DX,AL				;RESTORE TO CONTROLLER
	 ;
	 ;* All is well, return OK.
	 ;
	 MOV	DX,CPLOK			;OK RETURN
	 ;
SINITX:	 POP	CX				;RECOVER REGS
	 POP	DS
	 POP	DI
	 IRET
	 ;
	 SUBTTL	Soft Interrupt Finit
	 %OUT	Soft Interrupt Finit
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 1:  SFINI							*
	 ;*								*
	 ;* If driver is already inactive, simply returns 'OK' result.	*
	 ;* Otherwise, things are set back to the state when it was	*
	 ;* initialized. Extensive tests are made that everything works	*
	 ;* properly. If some problem is present, the finitialization	*
	 ;* not performed and an error is reported in DX. Otherwies, DX	*
	 ;* returns CPLOK.						*
	 ;***************************************************************
	 ;
	 ;* Finitialize inactive driver (dummy operation).
	 ;
SFINII:	 MOV	DX,CPLOK			;OK COMPLETION CODE
	 JMP	SHORT SFINI0			;AND RETURN
	 ;
	 ;* Finitialize active driver (normal finitialization).
	 ;
SFINIA:	 CALL	FINITIT				;DO THE JOB
SFINI0:	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 ;***************************************************************
	 ;* FINITIT							*
	 ;* This one does the real finit work for FINIT routines. Also	*
	 ;* called by SINITA.						*
	 ;***************************************************************
	 ;
FINITIT	 proc near
	 ;
	 ;* Reset the MPU401, taking all precautions.
	 ;
	 MOV	DX,WORD PTR ENAPORT		;ENABLE MASK PORT
	 IN	AL,DX				;GET CURRENT SETTINGS
	 OR	AL,BYTE PTR DISMASK		;DISABLE INTERRUPTS
	 OUT	DX,AL				;RESTORE TO CONTROLLER
	 ;
	 ;* Read any data there is in the MPU401 (max 200 bytes).
	 ;
	 MOV	DX,WORD PTR CTLPORT		;CONTROL PORT ADDRESS
	 PUSH	CX				;SAVE REG USED AS DOWN-COUNTER
	 MOV	CX,200				;MAX 200 BYTE
FINITI1: IN	AL,DX				;GET STATUS
	 AND	AL,DSRBIT			;SEE IF DATA EMPTY
	 JNZ	FINITI3				;THEN JUMP
	 ;
	 DEC	CX				;ADJUST TIMEOUT
	 JZ	FINITI2				;ERROR IF TIMEOUT
	 ;
	 DEC	DX				;DATA PORT
	 IN	AL,DX				;READ A BYTE
	 INC	DX				;CTRL PORT
	 JMP	FINITI1				;LOOP AGAIN
	 ;
FINITI2: POP	CX
	 MOV	DX,CPLMOFL			;MPU DATA OFLO ERROR CODE
	 RET
	 ;
	 ;* MPU401 buffer empty. Await MPU401 ready for command.
	 ;
FINITI3: MOV	CX,WORD PTR MPUDELY		;TIMEOUT COUNT
FINITI4: IN	AL,DX				;GET STATUSS
	 AND	AL,DRRBIT			;MPU READY FOR COMMAND
	 JZ	FINITI5				;JUMP IF SO
	 ;
	 DEC	CX				;BUMP TIMEOUT
	 JNZ	FINITI4				;LOOP IF NOT
	 ;
	 JMP	SHORT FINITI7			;TIMEOUT ERROR
	 ;
	 ;* MPU401 ready for command. Reset it to known final state.
	 ;
FINITI5: MOV	AL,MPURES			;RESET COMMAND
	 OUT	DX,AL
	 ;
	 MOV	CX,WORD PTR MPUDELY		;TIMEOUT DELAY
FINITI6: IN	AL,DX				;GET STATUS
	 AND	AL,DSRBIT			;SEE IF DATA THERE
	 JZ	FINITI8				;THEN JUMP
	 ;
	 DEC	CX				;BUMP TIMEOUT
	 JNZ	FINITI6				;LOOP IF NON-0
	 ;
FINITI7: POP	CX
	 MOV	DX,CPLTERR			;TIMEOUT ERROR
	 RET
	 ;
	 ;* MPU401 replied with a byte. Check it's ACK.
	 ;
FINITI8: DEC	DX				;DATA PORT
	 IN	AL,DX				;GET BYTE
	 CMP	AL,MPUACK			;SHOULD BE ACKNOWLEDGE
	 JZ	FINITI9				;JUMP IF OK
	 ;
	 POP	CX				;RECOVE REG
	 MOV	DX,CPLPRTE			;PROTOCOL ERROR
	 RET
	 ;
FINITI9: POP	CX				;RECOVER REG
	 ;
	 ;* MPU401 properly reset. Now restore hardware int enable
	 ;* status in 8259.
	 ;
	 MOV	AH,BYTE PTR ORGENA		;GET ORIGINAL SETTING
	 AND	AH,BYTE PTR DISMASK		;ISOLATE OUR BIT
	 MOV	DX,WORD PTR ENAPORT		;ENABLE MASK PORT
	 IN	AL,DX				;GET CURRENT SETTINGS
	 AND	AL,BYTE PTR ENAMASK		;REMOVE OUR BIT
	 OR	AL,AH				;INSERT IT
	 OUT	DX,AL				;RESTORE TO CONTROLLER
	 ;
	 ;* Restore original hardware interrupt vector to segment 0.
	 ;
	 PUSH	DS
	 XOR	AX,AX				;ACCESS SEGMENT 0
	 MOV	DS,AX
	 MOV	DI,CS:WORD PTR HWVECAD		;VECTOR LOCATION
	 MOV	AX,CS:WORD PTR OHWVEC		;GET OFFSET
	 CLI					;SHUT OFF DOING THIS
	 MOV	WORD PTR [DI],AX		;RESTORE IT
	 MOV	AX,CS:WORD PTR OHWVEC+2		;GET SEGMENT
	 MOV	WORD PTR 2[DI],AX
	 STI
	 POP	DS
	 ;
	 ;* Now finitialize some values and return.
	 ;
	 MOV	AX,OFFSET FNCTABI		;INACTIVE DRIVER JUMP TABLE
	 MOV	WORD PTR FNCTAB,AX		;USE IT IN FUTURE
	 MOV	WORD PTR STATREC,0		;DRIVER INACTIVE
	 ;
	 MOV	DX,CPLOK
	 RET
	 ;
FINITIT	 endp
	 ;
	 SUBTTL	Soft Interrupt Driver Status
	 %OUT	Soft Interrupt Driver Status
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 2:	SSTAT							*
	 ;*								*
	 ;* Get driver status. Returns status in AX as follows:		*
	 ;*								*
	 ;* Bit		Flags						*
	 ;*								*
	 ;* 0	Overflow has occured in the data buffer.		*
	 ;* 1	Overflow has occured in the exclusive buffer.		*
	 ;* 2	Timeout error has occured in the MPU401 protocol	*
	 ;* 3	A protocol error has been found in the input from the	*
	 ;*	MPU401 - unexpected bytes arrived. (Note that this	*
	 ;*	driver currently does not support all possible valid	*
	 ;*	data sequences).					*
	 ;* 8	Unread data in data buffer.				*
	 ;* 9	Unread data in exclusive buffer.			*
	 ;* 10	Last MPU command code has been acknowledged.		*
	 ;* 14	The machine is not a PC-XT, but an AT.			*
	 ;* 15	Driver is in the active state.				*
	 ;*								*
	 ;* SSTAT does not return any error status. DX returns two	*
	 ;* bytes of error data (the two last bytes read from the	*
	 ;* MPU401). DX is only relevant if a protocol error has	*
	 ;* occured (indicated by bit 3 of AX).				*
	 ;***************************************************************
	 ;
	 ;* Include AT bit by special test.
	 ;
SSTAT:	 MOV	AL,BYTE PTR ATFLAG		;SEE IF WE ARE AN AT
	 AND	AX,0FFH
	 JZ	SSTAT1
	 ;
	 MOV	AX,4000H			;SET AT BIT
	 ;
SSTAT1:	 OR	AX,WORD PTR STATREC		;ADD OTHER BITS
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 SUBTTL	Soft Interrupt Error Reset
	 %OUT	Soft Interrupt Error Reset
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 2:	SRERR							*
	 ;*								*
	 ;* Reset all error flags.					*
	 ;* SRERR does not return any error status. DX is not set.	*
	 ;***************************************************************
	 ;
SRERR:	 MOV	BYTE PTR STATREC,0		;CLEAR ERROR HALF OF STATREC
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 SUBTTL	Soft Interrupt Set Timer
	 %OUT	Soft Interrupt Set Timer
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 4:  SSTIM	Set timer					*
	 ;*								*
	 ;* The internal timer is initialized with the value in DX and	*
	 ;* CX. DX holds the high order word. 				*
	 ;* SSTIM does not return any error status. DX is not set.	*
	 ;***************************************************************
	 ;
SSTIM:	 ADD	CX,CX				;SHIFT UP INPUT VALUE 2 BITS
	 ADC	DX,DX
	 ADD	CX,CX
	 ADC	DX,DX
	 CLI					;DON'T CHANGE BETWEEN WRITES
	 MOV	WORD PTR TIMER,CX		;SET LOW WORD
	 MOV	WORD PTR TIMER+2,DX		;SET HIGH WORD
	 STI					;ALLOW CHANGES
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 ;
	 SUBTTL	Soft Interrupt Read Timer
	 %OUT	Soft Interrupt Read Timer
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 5:  SRTIM	Read timer					*
	 ;*								*
	 ;* The internal timer is read (if neccessary MPU401 time is	*
	 ;* requested and included), and the value is returned in DX	*
	 ;* and AX. DX holds the high order word. Note that this func-	*
	 ;* tion does not return any error status, but DX will hold the	*
	 ;* high order word of the return value. If an error should	*
	 ;* occur, the only way to check it is by reading the status	*
	 ;* record (with SSTAT) and check the error flags.		*
	 ;***************************************************************
	 ;
	 ;* Entry for active driver - requst fraction from MPU401
	 ;
SRTIMA:	 MOV	DL,MPUGRCT			;GET MPU RECORD COUNTER
	 CALL	PUTCMD				;SEND THE COMMAND
	 ;
	 ;* Entry for inactive driver
	 ;
SRTIMI:	 MOV	DX,WORD PTR MPUDELY		;TIMEOUT DELAY
	 ;
SRTIM1:	 TEST	BYTE PTR STATREC+1,4		;LAST CMD ACK'ED?
	 JNZ	SRTIM2
	 ;
	 DEC	DX				;BUMP TIMEOUT
	 JNZ	SRTIM1				;LOOP IF NO TIMEOUT
	 ;
	 OR	BYTE PTR STATREC,4		;SET TIMEOUT ERROR BIT
	 ;
SRTIM2:	 CLI					;DON'T CHANGE BETWEEN READS
	 MOV	AX,WORD PTR TIMER		;READ LOW WORD
	 MOV	DX,WORD PTR TIMER+2		;READ HIGH WORD
	 STI					;ALLOW CHANGES
	 RCR	DX,1				;SHIFT RESULT RIGHT 2
	 RCR	AX,1
	 RCR	DX,1
	 RCR	AX,1
	 AND	DH,3FH				;CLEAR SHIFTED GARBAGE
	 POP	DS				;RECOVER REGSS
	 POP	DI
	 IRET
	 ;
	 SUBTTL	Soft Interrupt Flush Buffer
	 %OUT	Soft Interrupt Flush Buffer
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 6:  SFBUF	Flush Buffer					*
	 ;*								*
	 ;* Flush input buffer. No error possible, DX not set.		*
	 ;***************************************************************
	 ;
SFBUF:	 XOR	AX,AX				;CLEAR TO 0
	 CLI					;NO INT WHILE WORKING
	 MOV	WORD PTR BFPUT,AX		;CLEAR BUFFER POINTERS
	 MOV	WORD PTR BFPIC,AX
	 MOV	WORD PTR NENT,AX		;CLEAR BYTE COUNT
	 AND	BYTE PTR STATREC+1,0FEH		;CLEAR DATA FLAG
	 STI
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 SUBTTL	Soft Interrupt Read Buffer
	 %OUT	Soft Interrupt Read Buffer
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 7:  SRBUF	Read Buffer					*
	 ;*								*
	 ;* If the driver is inactive, nothing is done and an error is	*
	 ;* returned. If driver is active, waits for a data byte in	*
	 ;* the buffer and then returns it. DX is CPLOK in this case.	*
	 ;***************************************************************
	 ;
	 ;* Read inactive driver is an error
	 ;
SRBUFI:	 MOV	DX,CPLPASS			;ERROR CODE
	 XOR	AX,AX				;CLEAR RETURN VALUE
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 ;* Read active driver (normal entry)
	 ;
SRBUFA:	 TEST	BYTE PTR STATREC+1,1		;TEST IF DATA THERE
	 JZ	SRBUFA
	 ;
	 MOV	DX,WORD PTR BFADR		;GET BUFFER OFFSET
	 MOV	DI,WORD PTR BFPIC		;GET PICK POINTER
	 ADD	DX,DI				;GET BUFFER POS
	 XCHG	DI,DX				;PUT IN INDEX REG
	 PUSH	DS
	 MOV	DS,WORD PTR BFADR+2		;GET BUFFER SEGMENT
	 MOV	AL,[DI]				;GET BUFFER DATA BYTE
	 POP	DS
	 ;
	 INC	DX				;NEXT BUF POS
	 CMP	DX,WORD PTR BFSIZ		;CHECK IF WRAP AROUND
	 JB	SRBUFA1				;JUMP IF NOT
	 ;
	 XOR	DX,DX
	 ;
SRBUFA1: MOV	WORD PTR BFPIC,DX		;UPDATE POINTER
	 CLI					;NO INTS NOW
	 DEC	WORD PTR NENT			;ADJUST BUFFER CONTENTS
	 JG	SRBUFA2				;IF STILL MORE
	 ;
	 AND	BYTE PTR STATREC+1,0FEH		;CLEAR DATA FLAG
	 ;
SRBUFA2: STI					;ALLOW IT NOW
	 XOR	AH,AH				;CLEAR HIGH RETURN VALUE
	 MOV	DX,CPLOK			;RETURN CODE
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 SUBTTL	Soft Interrupt Test Buffer
	 %OUT	Soft Interrupt Test Buffer
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 8:  STBUF	Test Buffer					*
	 ;*								*
	 ;* If the driver is inactive, nothing is done and an error is	*
	 ;* returned. If driver is active, returns 0 in AX if there is	*
	 ;* no data in the buffer waitung to be read. Returns 1 if data	*
	 ;* is available. DX set to CPLOK in this case.			*
	 ;***************************************************************
	 ;
	 ;* Test for inactive driver is an error
	 ;
STBUFI:	 MOV	DX,CPLPASS			;ERROR CODE
	 MOV	AX,1				;PRETEND BYTE TO AVOID HANGUP
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 ;* Test for activer driver (normal entry)
	 ;
STBUFA:	 XOR	AX,AX				;ASSUME NO DATA
	 TEST	BYTE PTR STATREC+1,1		;SEE IF DATA
	 JZ	STBUFA1				;JUMP IF NOT
	 ;
	 INC	AX				;SET IT TO 1
	 ;
STBUFA1: MOV	DX,CPLOK			;OK RETURN
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 SUBTTL	Soft Interrupt Define Exclusive Buffer
	 %OUT	Soft Interrupt Define Exclusive Buffer
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 9:  SDXBF	Define exclusive buffer				*
	 ;*								*
	 ;* If the driver is inactive, an error is returned. If the	*
	 ;* driver is active, and if if BX is non-zero, then DX:CX is	*
	 ;* the address of a non-default exclusive buffer, and BX is	*
	 ;* the size of it. If BX is 0, the exclusive buffer is un-	*
	 ;* defined, i.e. exclusive data is unbuffered. This is also	*
	 ;* the default after the driver is initialized. The buffer	*
	 ;* size must be >= 512, and <= 32768.				*
	 ;***************************************************************
	 ;
	 ;* Refuse if driver inactive
	 ;
SDXBFI:	 MOV	DX,CPLPASS			;ERROR RETURN CODE
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 ;* Entry for active driver (normal case)
	 ;
SDXBFA:  OR	BX,BX				;CHECK IF UNDEF. BUFFER?
	 JZ	SDXBF2
	 ;
	 CMP	BX,MINBSIZ			;CHECK IF BIG ENOUGH?
	 JB	SDXBF1				;JUMP IF NOT
	 ;
	 CMP	BX,MAXBSIZ			;CHECK IF TOO BIG?
	 JBE	SDXBF2				;JUMP IF OK
	 ;
SDXBF1:	 MOV	DX,CPLBERR			;BAD BUFFER CPL CODE
	 POP	DS
	 POP	DI
	 IRET					;ERROR RETURN
	 ;
SDXBF2:	 MOV	WORD PTR XBFADR,CX		;INSTALL OFFSET
	 MOV	WORD PTR XBFADR+2,DX		;INSTALL EGMENT
	 MOV	DX,CPLOK			;OK COMPLETION CODE
	 ;
	 XOR	AX,AX				;FOR CLEARING THINGS
	 CLI					;NO INT WHILE SETTING
	 MOV	WORD PTR XBFSIZ,BX		;INSTALL SIZE
	 MOV	WORD PTR XBFPUT,AX		;CLEAR BUFFER POINTERS
	 MOV	WORD PTR XBFPIC,AX
	 MOV	WORD PTR XNENT,AX		;CLEAR BUFFER CONTENTS
	 AND	WORD PTR STATREC,0FDFDH		;CLEAR XBUF RELATED BITS
	 POP	DS
	 POP	DI
	 IRET					;RESTORES INT STATUS
	 ;
	 SUBTTL	Soft Interrupt Flush Exclusive Buffer
	 %OUT	Soft Interrupt Flush Exclusive Buffer
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 10: SFXBF	Flush exclusive buffer				*
	 ;*								*
	 ;* Flush exclusive buffer. No error possible.			*
	 ;***************************************************************
	 ;
	 ;* Clear buffer pointers etc.
	 ;
SFXBF:	 MOV	DX,CPLOK			;RETURN OK CODE
	 POP	DS				;RECOVER REGS
	 POP	DI
	 ;
 	 XOR	AX,AX				;CLEAR TO 0
	 CLI					;NO INT WHILE WORKING
	 MOV	WORD PTR XBFPUT,AX		;CLEAR BUFFER POINTERS
	 MOV	WORD PTR XBFPIC,AX
	 MOV	WORD PTR XNENT,AX		;CLEAR BYTE COUNT
	 AND	BYTE PTR STATREC+1,0FDH		;CLEAR DATA FLAG
	 IRET					;RESTORES INT STATUS
	 ;
	 SUBTTL	Soft Interrupt Read Exclusive Buffer
	 %OUT	Soft Interrupt Read Exclusive Buffer
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 11: SRXBF	Read Exclusive Buffer				*
	 ;*								*
	 ;* If the driver is inactive, or the driver is active but no	*
	 ;* exclusive buffer is defined, nothing is done and an error	*
	 ;* is returned. If driver is active and has a buffer, waits	*
	 ;* for a data byte and then returns it. DX returns CPLOK in	*
	 ;* this case.							*
	 ;***************************************************************
	 ;
	 ;* Read inactive driver is an error
	 ;
SRXBFI:	 MOV	DX,CPLPASS			;ERROR CODE
	 XOR	AX,AX				;CLEAR RETURN VALUE
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 ;* Read from active driver (normal entry)
	 ;
SRXBFA:	 MOV	AX,WORD PTR XBFSIZ		;SEE IF THERE IS A BUFFER?
	 AND 	AX,AX
	 JNZ	SRXBFA1				;JUMP IF A BUFFER THERE
	 ;
	 ;* Read non-existent buffer is an error
	 ;
	 MOV	DX,CPLNOXB			;NO EXCLUSIVE BUFFER
	 POP	DS
	 POP	DI
	 IRET					;ERROR RETURN
	 ;
SRXBFA1: TEST	BYTE PTR STATREC+1,2		;TEST IF DATA THERE
	 JZ	SRXBFA1
	 ;
	 MOV	DX,WORD PTR XBFADR		;GET BUFFER OFFSET
	 MOV	DI,WORD PTR XBFPIC		;GET PICK POINTER
	 ADD	DX,DI				;GET BUFFER POS
	 XCHG	DI,DX				;PUT IN INDEX REG
	 PUSH	DS
	 MOV	DS,WORD PTR XBFADR+2		;GET BUFFER SEGMENT
	 MOV	AL,[DI]				;GET BUFFER DATA BYTE
	 POP	DS
	 ;
	 INC	DX				;NEXT BUF POS
	 CMP	DX,WORD PTR XBFSIZ		;CHECK IF WRAP AROUND
	 JB	SRXBFA2				;JUMP IF NOT
	 ;
	 XOR	DX,DX
	 ;
SRXBFA2: MOV	WORD PTR XBFPIC,DX		;UPDATE POINTER
	 CLI					;NO INTS NOW
	 DEC	WORD PTR XNENT			;ADJUST BUFFER CONTENTS
	 JG	SRXBFA3				;IF STILL MORE
	 ;
	 AND	BYTE PTR STATREC+1,0FDH		;CLEAR DATA FLAG
	 ;
SRXBFA3: STI					;ALLOW IT NOW
	 XOR	AH,AH				;CLEAR HIGH RETURN VALUE
	 MOV	DX,CPLOK			;RETURN CODE
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 SUBTTL	Soft Interrupt Test Exclusive Buffer
	 %OUT	Soft Interrupt Test Exclusive Buffer
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 12: STXBF	Test Exclusive Buffer				*
	 ;*								*
	 ;* If the driver is inactive, nothing is done and an error is	*
	 ;* returned. If driver is active, returns 0 in AX if there is	*
	 ;* no data in the buffer waiting to be read. Returns 1 if data	*
	 ;* is available. DX set to CPLOK in this case.			*
	 ;***************************************************************
	 ;
	 ;* Test for inactive driver is an error
	 ;
STXBFI:	 MOV	DX,CPLPASS			;ERROR CODE
	 MOV	AX,1				;PRETEND BYTE TO AVOID HANGUP
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 ;* Test for active driver (normal entry)
	 ;
STXBFA:	 XOR	AX,AX				;ASSUME NO DATA
	 TEST	BYTE PTR STATREC+1,2		;SEE IF DATA
	 JZ	STXBFA1				;JUMP IF NOT
	 ;
	 INC	AX				;SET IT TO 1
	 ;
STXBFA1: MOV	DX,CPLOK			;OK RETURN
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 SUBTTL	Soft Interrupt Send Command
	 %OUT	Soft Interrupt Send Command
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 13: SOCMD	Send a command byte				*
	 ;*								*
	 ;* If the driver is inactive, nothing is done and an error is	*
	 ;* returned. If driver is active, waits until the previous	*
	 ;* command has finished processing. Then sends the command	*
	 ;* byte (in DL). Set TIMEACK non-0 if the command requests	*
	 ;* record counter contents (used by SRTIM).			*
	 ;***************************************************************
	 ;
	 ;* Refuse to send if driver inactive.
	 ;
SOCMDI:	 MOV	DX,CPLPASS			;ERROR RETURN CODE
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 ;* Normal entry to send a command byte.
	 ;
SOCMDA:	 CALL	PUTCMD				;SEND OUT THE COMMAND
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 ;***************************************************************
	 ;* PUTCMD							*
	 ;*								*
	 ;* Writes a command byte from DL to the MPU401. Returns a	*
	 ;* completion code in DX. DS must be = CS when entering!	*
	 ;***************************************************************
	 ;
PUTCMD	 proc near
	 ;
	 MOV	AX,WORD PTR MPUDELY		;TIMEOUT COUNT
	 ;
PUTCMD1: TEST	BYTE PTR STATREC+1,4		;CHECK IF LAST CMD ACK'ED
	 JNZ	PUTCMD2				;JUMP IF OK
	 ;
	 DEC	AX				;BUMP TIMEOUT
	 JNZ	PUTCMD1				;LOOP IF NO TIMEOUT YET
	 ;
	 MOV	DX,CPLTERR
	 RET
	 ;
PUTCMD2: PUSH	CX				;USE AS TIMEOUT COUNTER
	 MOV	AH,DL				;INPUT DATA BYTE
	 MOV	CX,WORD PTR MPUDELY		;TIMEOUT COUNT
PUTCMD3: MOV	DX,WORD PTR CTLPORT		;GET STATUS
	 IN	AL,DX
	 AND	AL,DRRBIT			;CHECK IF READY TO RECEIVE
	 JZ	PUTCMD4
	 ;
	 DEC	CX				;BUMP TIMEOUT
	 JNZ	PUTCMD3				;LOOP IF TIME LEFT
	 ;
	 POP	CX
	 MOV	DX,CPLTERR			;TIMEOUT ERROR CODE
	 RET
	 ;
PUTCMD4: POP	CX				;BALANCE STACK
	 MOV	BYTE PTR ACKTIME,0		;CLEAR TIME EXPECTATION FLAG
	 CMP	AH,MPUGRCT			;WILL WE EXPECT A DATA BYTE?
	 JNZ	PUTCMD5				;JUMP IF NOT
	 ;
	 MOV	BYTE PTR ACKTIME,0FFH		;ELSE FLAG WE DO
	 ;
PUTCMD5: AND	BYTE PTR STATREC+1,0FBH		;CLEAR ACK'ED BIT
	 MOV	AL,AH				;SEND THE COMMAND
	 OUT	DX,AL
	 ;
	 MOV	DX,CPLOK			;ALL WELL
	 RET
	 ;
PUTCMD	 endp
	 ;
	 SUBTTL	Soft Interrupt Send Data
	 %OUT	Soft Interrupt Send Data
	 PAGE
	 ;
	 ;***************************************************************
	 ;* 14: SODAT	Send a data byte				*
	 ;*								*
	 ;* If the driver is inactive, nothing is done and an error is	*
	 ;* returned. If driver is active, sends the data byte in DL	*
	 ;* to the MPU401. Returns error if output timeout.		*
	 ;***************************************************************
	 ;
	 ;* Refuse to send if driver inactive.
	 ;
SODATI:	 MOV	DX,CPLPASS			;ERROR RETURN CODE
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
	 ;* Normal entry to send a data byte.
	 ;
SODATA:	 PUSH	CX				;SAVE TIMEOUT REG
	 MOV	CX,WORD PTR MPUDELY		;TIMEOUT COUNT
	 ;
	 ;* Don't send data until last cmd has beed acknowledged
	 ;
SODAT1:	 TEST	BYTE PTR STATREC+1,4		;CHECK IF LAST CMD ACK'ED
	 JNZ	SODAT2				;JUMP IF OK
	 ;
	 DEC	CX				;BUMP TIMEOUT
	 JNZ	SODAT1				;LOOP IF NO TIMEOUT YET
	 ;
	 MOV	DX,CPLTERR			;TIMEOUT ERROR
	 JMP	SHORT SODAT5
	 ;
SODAT2:	 MOV	AH,DL				;INPUT DATA
	 MOV	CX,WORD PTR MPUDELY		;TIMEOUT COUNT
	 MOV	DX,WORD PTR CTLPORT		;CONTROL PORT
	 ;
SODAT3:	 IN	AL,DX				;CHECK IF READY TO RECEIVE
	 AND	AL,DRRBIT
	 JZ	SODAT4				;JUMP IF SO
	 ;
	 DEC	CX				;BUMP TIMEOUT COUNT
	 JNZ	SODAT3				;LOOP IF NO TIMEOUT
	 ;
	 MOV	DX,CPLTERR			;TIMEOUT ERROR
	 JMP	SHORT SODAT5
	 ;
SODAT4:	 DEC	DX				;DATA PORT
	 MOV	AL,AH				;SEND DATA
	 OUT	DX,AL
	 MOV	DX,CPLOK			;OK COMPLETION
	 ;
SODAT5:	 POP	CX
	 POP	DS				;RECOVER REGS
	 POP	DI
	 IRET
	 ;
SWIHNL	 endp
	 ;
	 SUBTTL	Hard Interrupt Handler
	 %OUT	Hard Interrupt Handler
	 PAGE
	 ;
	 ;***************************************************************
	 ;* Hardware Interrupt Handler.					*
	 ;*								*
	 ;* You come here when the hardware pulls the interrupt line.	*
	 ;***************************************************************
	 ;
HWIHNL	 proc far
	 ;
	 ;* First check if it was the MPU401 that interrupted.
	 ;
	 PUSH	AX				;SAVE REGS ON CALLER STACK
	 PUSH	DX
	 MOV	DX,CS:WORD PTR CTLPORT		;CHECK IF MPU401 INTERRUPT
	 IN	AL,DX				;BY POLLING DSRBIT
	 AND	AL,DSRBIT
	 JZ	OURINT				;JUMP IF KEYBOARD INT
	 ;
	 ;* Interrupt, but not by the MPU401 - go to original
	 ;* interrupt handler (this is abnormal practice on a PC,
	 ;* where each interrupt line should be driven by only
	 ;* one source, but, as we all know... )
	 ;
	 POP	DX				;RECOVER REGS
	 POP	AX
	 DB	JFAROPC				;JMP FAR OP-CODE
OHWVEC	 DD	0				;ORIGINAL HW VECTOR
	 ;
	 ;* Now set up to use own stack - we don't know how much
	 ;* stack the interrupted routine provides.
	 ;
OURINT:	 MOV	CS:WORD PTR TMPSP,SP		;SAVE CALLER'S SP
	 MOV	CS:WORD PTR TMPSS,SS		;AND SS
	 CLI					;DON'T ALLOW INTERRUPT NOW!
	 MOV	AX,CS
	 MOV	SS,AX
	 MOV	SP,OFFSET STKTOP		;OUR OWN STACK
	 STI
	 PUSH	DS				;SAVE REGS
	 PUSH	CX
	 PUSH	CS				;SET DS = CS
	 POP	DS
	 ;
	 ;* Get the interrupting byte from MPU401 and branch accordingly.
	 ;
HWILOOP: MOV	DX,WORD PTR DATPORT		;PORT TO READ
	 IN	AL,DX				;GET IT
	 ;
	 CMP	AL,MPUOFLO			;TIMER OVERFLOW?
	 JNZ	NOOFLO
	 ;
	 ;* Timer overflow, adjust our internal 32-bit timer.
	 ;
	 ADD	WORD PTR TIMER,OFLTIM		;ADD OVERFLOW VALUE
	 ADC	WORD PTR TIMER+2,0		;32 BIT ADD
	 JMP	GOTMSG				;DONE
	 ;
	 ;* Not a timer overflow.
	 ;
NOOFLO:	 CMP	AL,MPUACK			;MPU ACKNOWLEDGE?
	 JNZ	NOACK
	 ;
	 ;* MPU command acknowledge. Flag it in STATREC, and read
	 ;* any extra accompanying time byte.
	 ;
	 OR	BYTE PTR STATREC+1,4		;SET ACK BIT IN STATUS RECORD
	 AND	AL,BYTE PTR ACKTIME		;IS DATA ASSOCIATED WITH IT?
	 JNZ	MPUTIM				;JUMP DISTANCE TOO FAR
	 JMP	GOTMSG
	 ;
MPUTIM:	 CALL	GETDATA				;GET THE DATA
	 ADD	WORD PTR TIMER,AX		;ADD TO THE TIMER ACKUMULATOR
	 ADC	WORD PTR TIMER+2,0		;32 BIT ADD
	 MOV	BYTE PTR ACKTIME,0		;CLEAR THE FLAG
	 JMP	GOTMSG				;DONE
	 ;
	 ;* Not an MPU401 command acknowledgement.
	 ;
NOACK:	 CMP	AL,MPUMAXT			;IS IT A TIME BYTE?
	 JBE	TIMEBYT				;JUMP IF NOT
	 ;
	 ;* Not a leading time byte.
	 ;
	 CMP	AL,MPUSYSM			;SYSTEM MESSAGE?
	 JNZ	HWIERR0				;TREAT ALL OTHERS AS ERROR
	 ;
	 ;* MPU401 sent a system message
	 ;
	 CALL	GETDATA				;SEE WHAT SYSTEM MESSAGE
	 CMP	AL,MPUMIDX			;SEE IF MIDI EXCLUSIVE?
HWIERR0: JNZ	HWIERR				;TREAT AS ERROR IF NOT
	 ;
	 ;* MPU401 reported a system exclusive message. Read it (and
	 ;* store it if we have a buffer).
	 ;
	 PUSH	DI				;SAVE REGS
	 PUSH	BX
	 MOV	DX,WORD PTR XBFSIZ		;BUFFER SIZE
	 MOV	BX,WORD PTR XBFPUT		;PUTTER INDEX
	 MOV	DI,WORD PTR XBFADR		;BUFFER ADDRESS OFFSET
	 PUSH	DS
	 MOV	DS,CS:WORD PTR XBFADR+2		;BUFFER ADDRESS SEGMENT
	 ;
PUTXMSG: OR	DX,DX				;SEE IF WE HAVE A BUFFER?
	 JZ	PUTXMS2				;JUMP IF NOT
	 ;
	 MOV	CH,AL				;FOR BUFFER PUTTING
	 CALL	PUTBUF				;PUT IT IN BUFFER
	 OR	CS:BYTE PTR STATREC+1,2		;SET DATA FLAG IN STATREC
	 ;
PUTXMS1: MOV	CS:WORD PTR XBFPUT,BX		;UPDATE BUFFER INDEX
	 INC	CS:WORD PTR XNENT		;BUMP BYTES IN XBUF
	 CMP	DX,CS:WORD PTR XNENT		;SEE IF BUFFER OVERFLOW?
	 JA	PUTXMS2				;JUMP IF NOT
	 ;
	 DEC	CS:WORD PTR XNENT		;CANNOT INCREASE
	 OR	CS:BYTE PTR STATREC,2		;SET BUFFER OVERFLOW BIT
	 ;
PUTXMS2: TEST	AL,MPUSBIT			;SEE IF IT WAS A STATUS BYTE
	 JZ	PUTXMS3				;THEN LOOP ON
	 ;
	 CMP	AL,MPUMIDX			;CHECK IF NEW EXCLUSIVE STATUS
	 JNZ	PUTXMS4				;BREAK LOOP IF NOT
	 ;
PUTXMS3: PUSH	DX				;GETDATA CHANGES DX
	 CALL	GETDATA				;GET NEXT BYTE
	 POP	DX
	 JMP	PUTXMSG				;GO STORE IT
	 ;
PUTXMS4: POP	DS				;RECOVER REGS
	 POP	BX
	 POP	DI
	 JMP	GOTMSG
	 ;
	 ;* MIDI timing byte
	 ;
TIMEBYT: MOV	BYTE PTR LASTTIM,AL		;SAVE IT AS POTENTIAL ERROR
	 XOR	AH,AH				;CLEAR UPPER BYTE
	 ADD	WORD PTR TIMER,AX		;ADD TO THE TIMER ACKUMULATOR
	 ADC	WORD PTR TIMER+2,0		;32 BIT ADD
	 ;
	 CALL	GETDATA				;GET NEXT BYTE
	 TEST	AL,MPUSBIT			;SEE IF RUNNING STATUS BYTE
	 JNZ	RSTAT				;JUMP IF SO
	 ;
	 ;* Message without running status. Use recorded status.	
	 ;
	 MOV	CH,BYTE PTR MIDISTT		;USE OLD RUNNING STATUS
	 JMP	SHORT GETMSG
	 ;
	 ;* We got time byte + something that can be either running
	 ;* status or an MPU MARK
	 ;
RSTAT:	 CMP	AL,MPUSMAX			;MAX RUNSTAT VALUE
	 JBE	MPUSTAT				;IT'S NO MARK, GET RUN STATUS
	 ;
	 ;* MIDI mark came from MPU401. Read and check it.
	 ;
MPUMARK: CMP	AL,MPUNOOP			;NOOP WE JUST IGNORE
	 JZ	GOTMSG				;OTHERS WE TREAT AS ERROR
	 ;
HWIERR:	 MOV	BYTE PTR LASTERR,AL		;SAVE FOR ERROR REPORTS
	 OR	BYTE PTR STATREC,8		;SET ERROR BIT IN STATREC
	 JMP	GOTMSG
	 ;
	 ;* We got a running status byte + message. Save status and
	 ;* prepare to get the message.
	 ;
MPUSTAT: MOV	BYTE PTR MIDISTT,AL		;STORE AWAY
	 MOV	CH,AL				;KEEP IT IN BH
	 CALL	GETDATA				;AND GET NEXT BYTE
	 ;
	 ;* Get the message itself. We already have the first byte
	 ;* in AL, and current running status in CH.
	 ;
GETMSG:	 MOV	CL,AL				;FIRST BYTE TO CL
	 MOV	AL,CH				;GET STATUS TO AL
	 AND	AL,ATCHMSK			;MASK TO CHECK FOR 0CH/0DH
	 SUB	AL,ATOUCH			;SEE IF ONE OF THEM (0 IF NOT)
	 JZ	PUTMSG				;THEN GO STORE THEM
	 ;
	 CALL	GETDATA				;GET 2:ND BYTE TOO
	 ;
	 ;* We now have status in CH, first byte in CL and (if
	 ;* applicable) second byte in AL.
	 ;
PUTMSG:  MOV	DX,WORD PTR NENT		;SEE IF BUFFER FULL?
	 ADD	DX,8				;8 BYTES HEADROOM
	 CMP	DX,WORD PTR BFSIZ		;CHECK IF TOO MUCH?
	 JB	PUTMSG1				;JUMP IF ROOM IN BUFFER
	 ;
	 OR	BYTE PTR STATREC,1		;SET STATREC BUFFER OFLOW BIT
	 JMP	SHORT GOTMSG
	 ;
	 ;* Put the data bytes in the buffer.
	 ;
PUTMSG1: PUSH	DI				;SAVE REGS
	 PUSH	BX
	 MOV	DX,WORD PTR BFSIZ		;BUFFER SIZE
	 MOV	BX,WORD PTR BFPUT		;PUTTER INDEX
	 MOV	DI,WORD PTR BFADR		;BUFFER ADDRESS OFFSET
	 PUSH	DS
	 MOV	DS,CS:WORD PTR BFADR+2		;BUFFER ADDRESS SEGMENT
	 ;
	 CALL	PUTBUF				;PUT IT IN BUFFER
	 MOV	CH,CL				;FIRST BYTE
	 CALL	PUTBUF				;PUT IT IN BUFFER
	 MOV	CH,AL				;SECOND BYTE
	 CALL	PUTBUF				;PUT IT IN BUFFER
	 XOR	CH,CH				;A DUMMY BYTE
	 CALL	PUTBUF				;PUT IT IN BUFFER
	 POP	DS				;RECOVER REGISTERS
	 MOV	WORD PTR BFPUT,BX		;UPDATE BUFFER INDEX
	 POP	BX
	 POP	DI
	 OR	BYTE PTR STATREC+1,1		;SET DATA FLAG IN STATREC
	 ADD	WORD PTR NENT,4			;UP BUFFER OCCUPANCY
	 ;
	 ;* MPU Message processed. Check if more data there.
	 ;
GOTMSG:	 MOV	DX,WORD PTR CTLPORT		;SEE IF ANOTHER MESSAGE?
	 IN	AL,DX
	 AND	AL,DSRBIT			;CHECK STATUS
	 JNZ	HWIEXIT				;IF NOTHING THERE, EXIT
	 JMP	HWILOOP				;ELSE GO GET IT
	 ;
	 ;* Done, Make an EOI, restore the interrupted routine's
	 ;* stack and return to it.
	 ;
HWIEXIT: MOV	DX,WORD PTR EOIPORT		;INT CONTROLLER CMD PORT
	 MOV	AL,EOICMD
	 POP	CX				;RESTORE REGS
	 POP	DS
	 CLI
	 MOV	SP,CS:WORD PTR TMPSP		;RESTORE CALLER'S SP
	 MOV	SS,CS:WORD PTR TMPSS		;AND SS
	 OUT	DX,AL				;SEND THE EOI COMMAND
	 POP	DX				;RECOVER FROM CALLER STACK
	 POP	AX
	 IRET					;RESTORES INITIAL EI STATUS
	 ;
HWIHNL	 endp
	 ;
	 ;***************************************************************
	 ;* GETDATA							*
	 ;*								*
	 ;* Read a byte from the data port. Returns the value in AX.	*
	 ;* Sets the timeout error bit in STATREC if no data before	*
	 ;* time-out. If time-out, returns FAKEDAT in AX.		*
	 ;***************************************************************
	 ;
GETDATA	 proc near
	 ;
	 PUSH	CX				;SAVE REG
	 MOV	CX,CS:WORD PTR MPUDELY		;TIMEOUT VALUE
	 MOV	DX,CS:WORD PTR CTLPORT
	 ;
GETDAT1: IN	AL,DX
	 AND	AX,DSRBIT			;CHECK IF DATA THERE
	 JZ	GETDAT2				;THEN JUMP
	 ;
	 DEC	CX				;BUMP TIMEOUT
	 JNZ	GETDAT1				;LOOP IF NOT TIMED OUT
	 ;
	 POP	CX				;RECOVER REG
	 OR	CS:BYTE PTR STATREC,4		;SET TIMEOUT ERROR BIT
	 MOV	AX,FAKEDAT			;FAKED RETURN DATA
	 RET
	 ;
GETDAT2: POP	CX				;RECOVER REG
	 DEC	DX				;DATA PORT
	 IN	AL,DX				;GET DATA (AH=0)
	 RET
	 ;
GETDATA	 endp
	 ;
	 ;***************************************************************
	 ;* PUTBUF							*
	 ;*								*
	 ;* Put CH at the location addressed by DS:BX[DI], and adjust	*
	 ;* BX properly thereafter. DX holds the buffer size. It is	*
	 ;* assumed that any buffer overflow checks have already been	*
	 ;* made.							*
	 ;***************************************************************
	 ;
PUTBUF	 proc near
	 ;
;* !!! Does not this basic addressing mode exist on the 8088/86???!!:
;*	 MOV	DS:BYTE PTR BX[DI],CH		;STORE THE BYTE
;* !!! Trivial on 68K [MOVE.B 0(Dn,An),Dm] - This makes me upset!!!
;* !!! I'm sure you can do it, but maybe not with any regs?
	 ;
	 PUSH	DI				;SAVE DI
	 XCHG	BX,DI				;SAVE BX
	 ADD	BX,DI				;TOTAL OFFSET INTO SEGMENT
	 XCHG	BX,DI				;RECOVER BX
	 MOV	DS:[DI],CH				;PUT IT DOWN
	 POP	DI				;RECOVER DI
	 ;
	 INC	BX				;NEXT BUFFER POS.
	 CMP	BX,DX				;CHECK IF BEYOND BUFFER
	 JB	PUTBUF1
	 ;
	 XOR	BX,BX				;WRAP TO BUFFER START
	 ;
PUTBUF1: RET
	 ;
PUTBUF	 endp
	 ;
	 SUBTTL	Driver Init Code / Buffer Area
	 %OUT	Driver Init Code / Buffer Area
	 PAGE
	 ;
	 ;***************************************************************
	 ;* INIT (0)							*
	 ;*								*
	 ;* This code performs the INIT request, done only once when	*
	 ;* starting the operating system. This code will be overwrit-	*
	 ;* ten by the MIDI buffer later. Therefore it is located at	*
	 ;* the end of the driver.					*
	 ;***************************************************************
	 ;
DBUF	 EQU	$				;START OF MIDI BUFFER
	 ;
INIT	 proc near
	 ;
	 ;* First of all, determine if we are an XT or an AT.
	 ;* Set MPU401 delay accordingly.
	 ;
	 MOV	WORD PTR MPUDELY,2000H		;TIME FOR AN XT
	 MOV	CX,SP				;SAVE CURRENT SP
	 PUSH	SP				;PUSH IT
	 POP	AX				;GET THE VALUE WE JUST PUSHED
	 CMP	AX,CX				;IS IT SAME AS BEFORE PUSH?
	 JNE	INITXT				;NO, WE ARE AN 8086/88
	 ;
	 MOV	CS:BYTE PTR ATFLAG,1		;80286, FLAG IT
	 MOV	WORD PTR MPUDELY,6000H		;TIME FOR AN AT
	 ;
	 ;* Now install default values.
	 ;
INITXT:	 MOV	CS:WORD PTR DATPORT,DDATPORT	;SET UP DEFAULT PORT ADDRESSES
	 MOV	CS:WORD PTR CTLPORT,DCTLPORT
	 MOV	CS:BYTE PTR HRDINT,DHRDINT	;AND INT NUMBERS
	 MOV	CS:BYTE PTR SFTINT,DSFTINT
	 MOV	CS:WORD PTR IBSIZ,DIBSIZ	;AND BUILT-IN BUFFER SIZE
	 MOV	CS:WORD PTR FNCTAB,OFFSET FNCTABI ;INACTIVE DRIVER JUMP TABLE
	 ;
	 ;* Now read and act upon any ASCII parameters. They will
	 ;* supercede the defaults.
	 ;
	 MOV	DS,ES:WORD PTR PMSOFS[BX]	;GET PARAMETER SEGMENT
	 MOV	SI,ES:WORD PTR PMOOFS[BX]	;GET PARAMETER OFFSET
	 ;
PARMLP:	 MOV	AL,BYTE PTR[SI]			;GET CHARACTER
	 INC	SI
	 CMP	AL,'/'				;'/' STARTS ALL PARAMETERS
	 JZ	PARM
	 CMP	AL,ASCILF			;IS IT AN LF (END OF STRING)?
	 JNZ	PARMLP				;LOOP IF NOT
	 ;
	 JMP	INIT2				;END OF PARM STRING
	 ;
PARM:	 MOV	AL,BYTE PTR[SI]			;GET WHAT PARAM
	 INC	SI
	 AND	AL,UCMASK			;UPPER CASE IT
	 MOV	DX,OFFSET ERRMSG1		;ERROR ANTICIPATED
	 MOV	CS:BYTE PTR ERRMS1I,AL		;PUT PARM NAME THERE
	 CMP	AL,'B'				;BUILT-IN BUFFER SIZE?
	 JZ	PARM1
	 CMP	AL,'H'				;HARDWARE INT NUMBER?
	 JZ	PARM1
	 CMP	AL,'P'				;PORT ADDRESS?
	 JZ	PARM1
	 CMP	AL,'S'				;SOFTWARE INT NUMBER?
	 JNZ	PARMERR
	 ;
PARM1:	 MOV	AL,BYTE PTR[SI]			;GET COLON - SHOULD BE ONE!
	 INC	SI
	 MOV	DX,OFFSET ERRMSG2		;ANTICIPATE ERROR
	 CMP	AL,':'				;MUST BE A COLON!
	 JNZ	PARMERR
	 ;
	 CALL	GETHEX				;GET PARAMETER VALUE
	 MOV	AL,CS:BYTE PTR ERRMS1I		;GET PARM NAME
	 CMP	AL,'P'				;PORT ADDRESS?
	 JNZ	PARM2
	 ;
	 MOV	DX,OFFSET ERRMSG3		;ANTICIPATE ERROR
	 CMP	CX,PORTMAX			;MAX ALLOWED PORT ADDRESS
	 JA	PARMERR
	 ;
	 MOV	CS:WORD PTR DATPORT,CX		;NEW DATA PORT ADDRESS
	 INC	DX				;CTRL PORT ADDRES ONE HIGHER
	 MOV	CS:WORD PTR CTLPORT,CX
	 JMP	PARMLP
	 ;
PARM2:	 CMP	AL,'S'				;SOFTWARE INTERRUPT?
	 JNZ	PARM3
	 ;
	 MOV	DX,OFFSET ERRMSG4		;ANTICIPATE ERROR
	 CMP	CX,MAXSFTI			;SEE IF HIGHER THAN ALLOWED
	 JA	PARMERR
	 ;
	 CMP	CX,MINSFTI			;SEE IF LOWER THAN ALLOWED?
	 JB	PARMERR
	 ;
	 MOV	CS:BYTE PTR SFTINT,CL		;INSTALL NEW SOFT INT NUMBER
	 JMP	PARMLP
	 ;
PARM3:	 CMP	AL,'H'				;HARDWARE INTERRUPT?
	 JNZ	PARM5
	 ;
	 MOV	DX,OFFSET ERRMSG5		;ANTICIPATE ERROR
	 CMP	CX,0FH				;CHECK INT IS 0-0FH!
	 JA	PARMERR				;ELSE ERROR!
	 ;
	 MOV	AL,CS:BYTE PTR ATFLAG		;SEE IF AT
	 AND	AL,AL
	 JNZ	PARM4				;IF SO JUMP
	 ;
	 MOV	DX,OFFSET ERRMSG6		;ANTICIPATE ERROR
	 CMP	CL,7				;FOR XT, ONLY 0-7 ARE ALLOWED
	 JA	PARMERR
	 ;
PARM4:	 MOV	CS:BYTE PTR HRDINT,CL		;INSTALL NEW HARD INT NUMBER
	 JMP	PARMLP
	 ;
PARM5:	 MOV	DX,OFFSET ERRMSG7		;BUFSIZ - ANTICIPATE ERROR
	 CMP	CX,MINBSIZ			;CHECK VALUE LIMITS
	 JB	PARMERR
	 CMP	CX,MAXBSIZ
	 JA	PARMERR
	 ;
	 MOV	CS:WORD PTR IBSIZ,CX		;INSTALL NEW BUFFER SIZE
	 JMP	PARMLP
	 ;
	 ;* You come here when an invalid parameter has been found.
	 ;* Print an error message and stop scanning parameters.
	 ;* Leave things as they are and continue.
	 ;*
	 ;* (What you would want to do is print the error, then
	 ;* set the driver size to zero to avoid installing it.
	 ;* This is possible according to the PC-DOS tech man.
	 ;* HOWEVER, it *** DOES NOT WORK ***. MicroSoft them-
	 ;* selves do some tricks to overcome this in their
	 ;* Mouse Driver.)
	 ;
PARMERR: PUSH	CS				;MAKE DS=CS
	 POP	DS
	 PUSH	DX				;SAVE MSG ADDRESS
	 MOV	DX,OFFSET ERRMSG0
	 MOV	AH,PRTSTR
	 INT	SYSTEM				;TYPE DRIVER NAME
	 POP	DX
	 MOV	AH,PRTSTR			;TYPE IT
	 INT	SYSTEM
	 ;
;	 ;* Set size of driver to 0 (DOES NOT WORK - crashes the PC
;	 ;* during boot - bug in MSDOS's initialization code !).
;	 ;
;	 MOV	ES:WORD PTR EOFOFS[BX],0	;SET END OFFSET
;	 MOV	ES:WORD PTR ESGOFS[BX],CS	;SET END PARAGRAPH = BEGINING
;	 RET
	 ;
	 ;* Parameters found and processed. Now install the software
	 ;* interrupt vector after first saving the original value.
	 ;
INIT2:	 XOR	AX,AX				;CLEAR DS
	 MOV	DS,AX
	 MOV	AL,CS:BYTE PTR SFTINT		;GET SOFT INT
	 ADD	AX,AX				;MULT BY 4
	 ADD	AX,AX
	 MOV	SI,AX
	 MOV	AX,WORD PTR DS:[SI]		;GET OLD VECTOR'S OFFSET
	 MOV	CS:WORD PTR OSWVEC,AX		;SAVE IT
	 MOV	AX,WORD PTR DS:2[SI]		;GET SEGMENT
	 MOV	CS:WORD PTR OSWVEC+2,AX		;AND SAVE
	 ;
	 MOV	DS:WORD PTR [SI],OFFSET SWIHNL	;INSTALL SOFTWARE INTERRUPT
	 MOV	DS:WORD PTR 2[SI],CS		;HANDLER ADDRESS	
	 ;
	 ;* Now set up hardware interrupt dependent values.
	 ;
	 MOV	CX,OFFSET XTTAB			;ASSUME WE ARE AN XT
	 MOV	AL,CS:BYTE PTR ATFLAG		;CHECK THAT
	 AND	AX,0FFH				;CLEARS AH FOR LATER
	 JE	INIT3
	 ;
	 MOV	CX,OFFSET ATTAB			;NO, WE ARE AN AT
	 ;
INIT3:	 MOV	AL,CS:BYTE PTR HRDINT		;GET INT NUMBER
	 ADD	AX,AX				;SHIFT UP 2 (AH = 0)
	 ADD	AX,AX
	 ADD	AX,CX				;POINT TO PROPER TABLE ENTRY
	 MOV	SI,AX
	 MOV	AL,CS:BYTE PTR [SI]		;GET EOI PORT ADDRESS (8 BITS)
	 MOV	CS:BYTE PTR EOIPORT,AL		;INSTALL VALUE
	 INC	SI
	 MOV	AL,CS:BYTE PTR [SI]		;GET ENABLE PORT (8 BITS)
	 MOV	CS:BYTE PTR ENAPORT,AL		;INSTALL VALUE
	 INC	SI
	 MOV	AL,CS:BYTE PTR [SI]		;GET BIT MASK (8 BITS)
	 MOV	CS:BYTE PTR DISMASK,AL		;INSTALL DISABLE MASK
	 NOT	AL				;INVERT IT
	 MOV	CS:BYTE PTR ENAMASK,AL		;INSTALL ENABLE MASK
	 INC	SI
	 MOV	AL,CS:BYTE PTR [SI]		;GET VECTOR ADDR/4
	 XOR	AH,AH
	 ADD	AX,AX
	 ADD	AX,AX
	 MOV	CS:WORD PTR HWVECAD,AX		;INSTALL VECTOR ADDRESS
	 ;
	 ;* Now finally return driver values.
	 ;
	 PUSH	CS
	 POP	DS
	 MOV	DX,OFFSET SIGNON
	 MOV	AH,PRTSTR
	 INT	SYSTEM
	 ;
	 PUSH	CS				;GET CODE PARAGRAPH TO DX
	 POP	DX
	 MOV	AX,OFFSET DBUF + 16		;GET DRIVER'S LENGTH + EXTRA
	 ADD	AX,CS:WORD PTR IBSIZ		;ADD BUILT-IN BUFFER SIZE
	 MOV	CL,4				;SHIFT BY 4 TO GET # PARAGR.
	 AND	AL,0F0H				;CLEAR 4 LOW BITS
	 ROR	AX,CL
	 ADD	DX,AX				;END PARAGRAPH IN DX NOW
	 MOV	ES:WORD PTR EOFOFS[BX],0	;SET END OFFSET
	 MOV	ES:WORD PTR ESGOFS[BX],DX	;SET END PARAGRAPH
	 ;
	 RET
	 ;
INIT	 endp
	 ;
	 ;***************************************************************
	 ;* GETHEX							*
	 ;*								*
	 ;* Reads hex characters from string at DS:0[SI] and builds	*
	 ;* value in CX. Terminates on non-hex character. Only used	*
	 ;* during driver initialization. Overwritten by buffer.	*
	 ;***************************************************************
	 ;
GETHEX	 proc near
	 ;
	 XOR	CX,CX				;CLEAR RESULT
	 ;
GETHXLP: MOV	AL,BYTE PTR[SI]			;GET CHAR	 
	 CMP	AL,'0'				;SEE IF < DEC LOW
	 JB	GETHXEX				;THEN EXIT
	 ;
	 CMP	AL,'9'				;SEE IF DEC DIGIT
	 JBE	GETHX1				;THEN JUMP
	 ;
	 AND	AL,UCMASK			;MASK TO UPPER CASE
	 CMP	AL,'A'				;SEE IF < HEX DIGIT
	 JB	GETHXEX				;THEN EXIT
	 ;
	 CMP	AL,'F'				;SEE IF > HEX DIGIT
	 JA	GETHXEX				;THEN JUMP
	 ;
	 SUB	AL,7				;FOR ASCII 'A'-'F' OFFSET 
	 ;
GETHX1:	 AND	AX,0FH				;MASK DIGIT
	 ADD	CX,CX				;SHIFT CX UP 4
	 ADD	CX,CX
	 ADD	CX,CX
	 ADD	CX,CX
	 ADD	CX,AX				;ADD IN LAST DIGIT
	 INC	SI				;NEXT CHARACTER
	 JMP	GETHXLP
	 ;
GETHXEX: RET
	 ;
GETHEX	 endp
	 ;
	 SUBTTL	Initialization Tables
	 %OUT	Initialization Tables
	 PAGE
	 ;
	 ;***************************************************************
	 ;* Initialization tables					*
	 ;***************************************************************
	 ;
	 ;* These tables contain, in each entry:
	 ;*
	 ;* - the EOI command output port address,
	 ;* - the enable/disable interrupt port address,
	 ;* - the interrupt enable bit mask, (bit is a one),
	 ;* - the memory address where to put the vector for the
	 ;*   corresponding interrupt level (divided by 4 to fit
	 ;*   it in a byte).
	 ;
	 ;* First the table for an XT machine.
	 ;
XTTAB:	 DB	20H, 21H, 00000001B,   8		;DATA FOR XT INT 0
	 DB	20H, 21H, 00000010B,   9		;FOR XT INT 1
	 DB	20H, 21H, 00000100B, 0AH		;FOR XT INT 2
	 DB	20H, 21H, 00001000B, 0BH		;FOR XT INT 3
	 DB	20H, 21H, 00010000B, 0CH		;FOR XT INT 4
	 DB	20H, 21H, 00100000B, 0DH		;FOR XT INT 5
	 DB	20H, 21H, 01000000B, 0EH		;FOR XT INT 6
	 DB	20H, 21H, 10000000B, 0FH		;FOR XT INT 7
	 ;
	 ;* Now, the table for an AT machine.
	 ;
	 ;* !!!NOTE!!! The data in the ATTAB table is NOT CORRECT!!!
	 ;* (Due to lack of needed information about AT interrupts).
	 ;
ATTAB:	 DB	20H, 21H, 00000001B,   8		;DATA FOR AT INT 0
	 DB	20H, 21H, 00000010B,   9		;FOR AT INT 1
	 DB	20H, 21H, 00000100B, 0AH		;FOR AT INT 2
	 DB	20H, 21H, 00001000B, 0BH		;FOR AT INT 3
	 DB	20H, 21H, 00010000B, 0CH		;FOR AT INT 4
	 DB	20H, 21H, 00100000B, 0DH		;FOR AT INT 5
	 DB	20H, 21H, 01000000B, 0EH		;FOR AT INT 6
	 DB	20H, 21H, 10000000B, 0FH		;FOR AT INT 7
	 DB	20H, 21H, 00000001B,   8		;FOR AT INT 8
	 DB	20H, 21H, 00000010B,   9		;FOR AT INT 9
	 DB	20H, 21H, 00000100B, 0AH		;FOR AT INT 10
	 DB	20H, 21H, 00001000B, 0BH		;FOR AT INT 11
	 DB	20H, 21H, 00010000B, 0CH		;FOR AT INT 12
	 DB	20H, 21H, 00100000B, 0DH		;FOR AT INT 13
	 DB	20H, 21H, 01000000B, 0EH		;FOR AT INT 14
	 DB	20H, 21H, 10000000B, 0FH		;FOR AT INT 15
	 ;
	 SUBTTL	Error Messages
	 %OUT	Error Messages
	 PAGE
	 ;
	 ;***************************************************************
	 ;* Error messages						*
	 ;***************************************************************
	 ;
SIGNON:  DB	'--- Installing CMT MIDI  MPU401   Device Driver'
	 DB	' v1.30 ---',ASCICR, ASCILF,'$'
	 ;
ERRMSG0: DB	'MPU401 MIDI Driver: $'
	 ;
ERRMSG1: DB	'Unknown Init Parameter "'
ERRMS1I: DB	' "'				;CHAR TO BE INSERTED
	 DB	ASCICR,ASCILF,'$'
	 ;
ERRMSG2: DB	'No ":" After Parm Name'
	 DB	ASCICR,ASCILF,'$'
	 ;
ERRMSG3: DB	'Illegal Port Address'
	 DB	ASCICR,ASCILF,'$'
	 ;
ERRMSG4: DB	'Illegal Software Interrupt'
	 DB	ASCICR,ASCILF,'$'
	 ;
ERRMSG5: DB	'Illegal Hardware Interrupt'
	 DB	ASCICR,ASCILF,'$'
	 ;
ERRMSG6: DB	'Hard Int 8-15 Allowed On PC-AT Only'
	 DB	ASCICR,ASCILF,'$'
	 ;
ERRMSG7: DB	'Illegal Buffer Size'
	 DB	ASCICR,ASCILF,'$'
	 ;
CODSEG	 ENDS
	 IF1
	 %OUT	---------- Pass 1 Completed ----------
	 ENDIF
	 IF2
	 %OUT	---------- Pass 2 Completed ----------
	 ENDIF
	 SUBTTL	Symbols
	 END
