        COMMENT *

       DX-7 Voice Troll
       Copyright (c) 1986 Steve Rimmer

       May also be suitable for use as a 
       speadsheet, although it's not likely.

       *

MIDI_PORT      EQU     0330H   ;BASE OF MPU-401 PORTS
STATUS_PORT    EQU     MIDI_PORT+1     
COMMAND_PORT   EQU     MIDI_PORT+1
DATA_PORT      EQU     MIDI_PORT
SEND_READY     EQU     80H     ;BIT FOR DATA WAITING
DATA_READY     EQU     40H     ;BIT FOR CLEAR TO SEND
MPU_RESET      EQU     0FFH    ;COMMAND TO RESET MPU401
MPU_UART       EQU     03FH    ;COMMAND TO SET UART MODE
MPU_VERSION    EQU     0ACH    ;COMMAND TO REQUEST VERSION
MPU_REVISION   EQU     0ADH    ;COMMAND TO REQUEST REVISION

       CODEX   SEGMENT
       ASSUME  CS:CODEX, DS:CODEX, ES:CODEX
MAIN   PROC    FAR
       ORG     100H

MAIN   ENDP
START: CALL    CLRSCRN ;CLEAR THE TUBE
START00:       MOV     AL,MPU_RESET
       CALL    PUT_MPU ;RESET THE MPU-401
       
       CALL    ILPRT   ;SAY HELLO
       DB      16 DUP(205)
       DB      ' DX Voice Troll Copyright (c) 1986 Steve Rimmer '
       DB      16 DUP(205),0

       MOV     DX,011EH;SHOW 
       CALL    GOTOXY  ;... MPU-401 VERSION
       CALL    ILPRT
       DB      'MPU-401 version ',0
       CALL    GET_VERSION

       MOV     AL,MPU_UART
       CALL    PUT_MPU ;PUT MPU-401 IN UART MODE

       CALL    BLANK
       CALL    ILPRT   ;SHOW MENU
       DB      'S= send, R= receive, Q= get lost ',0
START0:CALL    GETCH   ;GET CHOICE
       CALL    TOUPPER
       CMP     AL,'S'  ;REQUEST TO SEND?
       JNE     START1  ;IF NOT, TRY RECEIVE
       CALL    LOAD_NAME       ;LOAD FILE NAME
       JC      ACCESS_DENIED   ;IF CARRY, BAD LOAD
       CALL    CHECKSUM;SEE IF IT'S A GOOD FILE
       JNE     CHECKSUM_ERROR  
       CALL    SEE_DX  ;SEE THE VOICES
       CALL    PUT_DX  ;SEND THEM TO THE DX
       JMP     GET_LOST;WANDER AWAY

START1:CMP     AL,'R'  ;REQUEST TO RECEIVE?
       JNE     START2  ;IF NOT, TRY RECEIVE
       CALL    SAVE_NAME       ;GET NAME OF FILE
       JC      ACCESS_DENIED   ;CARRY SAYS SOMETHING'S AMISS
       CALL    GET_DX  ;GET VOICES
       CALL    CHECKSUM;SEE IF IT'S A GOOD FILE
       JNE     CHECKSUM_ERROR  
       CALL    SEE_DX  ;SEE THE VOICES
       CALL    SAVE_DX ;SAVE THEM TO THE FILE
       JMP     GET_LOST;ABSCOND
       
START2:CMP     AL,'Q'  ;REQUEST TO SCRAM?
       JNE     START0  ;IF NOT, TRY AGAIN

GET_LOST:      MOV     AL,MPU_RESET    ;CLOSE UP SHOP
       CALL    PUT_MPU
       CALL    CLRSCRN ;CLEAR TUBE
       INT     20H     ;VAPOURIZE

CHECKSUM_ERROR:CALL    BLANK   ;SAY BAD CHECKSUM
       CALL    ILPRT
       DB      'Checksum error - zap any key',0
       CALL    GETCH
       JMP     GET_LOST

ACCESS_DENIED: CALL    BLANK   ;SAY BAD FILE
       CALL    ILPRT
       DB      'File error - zap any key',0
       CALL    GETCH
       JMP     GET_LOST

STUFF  PROC    NEAR
;THIS ROUTINE SENDS VOICES TO DX-7
PUT_DX:CALL    BLANK   ;SAY WHAT WE'RE UP TO
       CALL    ILPRT
       DB      'Sending voices',0
       MOV     SI,OFFSET CODE_END      ;POINT TO VOICES
PUT_DX1:       MOV     AL,[SI] ;GET BYTE
       PUSH    AX
       CALL    PUT_MIDI;SEND IT
       POP     AX
       INC     SI      ;POINT TO NEXT BYTE
       CMP     AL,0F7H ;CHECK FOR END OF VOICES
       JNE     PUT_DX1 ;LOOP 'TIL DONE
       CALL    BLANK   ;SAY ALL DONE
       CALL    ILPRT
       DB      'All done - Zap STORE on the DX-7',0
       RET

;THIS ROUTINE GETS A FILE NAME TO SAVE TO
SAVE_NAME:     CALL    BLANK   ;SHOW PROMPT
       CALL    ILPRT
       DB      'File name to save to: ',0
       MOV     BYTE PTR [CODE_END],64  ;MAXIMUM OF 64 CHARACTERS
       MOV     AH,0AH  ;GET STRING THROUGH DOS
       MOV     DX,OFFSET CODE_END
       INT     21H
       SUB     AX,AX   ;PUT TERMINATING NULL
       MOV     AL,BYTE PTR [CODE_END+1]
       MOV     SI,OFFSET CODE_END+2    ;...AT END OF STRING
       ADD     SI,AX
       MOV     BYTE PTR [SI],0
       MOV     DX,OFFSET CODE_END+2
       MOV     CX,0
       MOV     AH,3CH
       INT     21H     ;CREATE THE FILE
       MOV     WORD PTR [FILE_HANDLE],AX ;AND SAVE THE FILE HANDLE
       RET

;THIS ROUTINE SAVES THE VOICE FILE
SAVE_DX:       CALL    BLANK   ;SAY WHAT       
       CALL    ILPRT
       DB      'Saving voices',0
       MOV     AH,40H  ;WRITE FILE TO DISK
       MOV     DX,OFFSET HEADER
       MOV     CX,4200
       MOV     BX,WORD PTR [FILE_HANDLE]
       INT     21H
       MOV     AH,3EH  ;CLOSE FILE
       MOV     BX,WORD PTR [FILE_HANDLE]
       INT     21H
       RET

;THIS ROUTINE LOADS A VOICE FILE
LOAD_NAME:     CALL    BLANK   ;SPEAK
       CALL    ILPRT
       DB      'File name to load: ',0
       MOV     BYTE PTR [CODE_END],64
       MOV     AH,0AH  ;GET THE NAME 
       MOV     DX,OFFSET CODE_END
       INT     21H
       SUB     AX,AX
       MOV     AL,BYTE PTR [CODE_END+1]
       MOV     SI,OFFSET CODE_END+2
       ADD     SI,AX   ;PLACE TERMINATING NULL
       MOV     BYTE PTR [SI],0
       MOV     DX,OFFSET CODE_END+2
       MOV     AX,3D00H
       INT     21H     ;OPEN FILE
       JC      LOAD_NAME1
       MOV     WORD PTR [FILE_HANDLE],AX
       CALL    BLANK   ;SAY WHAT'S HAPPENING
       CALL    ILPRT
       DB      'Loading voices',0
       MOV     AH,3FH  ;INHALE VOICES
       MOV     DX,OFFSET HEADER
       MOV     CX,4200
       MOV     BX,WORD PTR [FILE_HANDLE]
       INT     21H
       JC      LOAD_NAME1
       MOV     AH,3EH  ;CLOSE FILE
       INT     21H
LOAD_NAME1:    RET

;THIS ROUTINE GETS VOICE DUMP INTO MEMORY
GET_DX:CALL    BLANK   ;VERBALIZE
       CALL    ILPRT
       DB      'Awaiting voice dump',0
       MOV     SI,OFFSET CODE_END      ;POINT TO FREE SPACE
GET_DX0:       CALL    GET_MIDI;GET A BYTE
       CMP     AL,0F0H ;CHECK FOR START OF
       JNE     GET_DX0 ;... VOICE DUMP
       MOV     [SI],AL ;SAVE THE BYTE
       INC     SI      ;BUMP THE POINTER
GET_DX1:       CALL    GET_MIDI;GET A BYTE
       MOV     [SI],AL ;SAVE IT
       INC     SI      ;BUMP THE POINTER
       CMP     AL,0F7H ;SEE IF IT'S END OF DUMP
       JNE     GET_DX1
       RET

;THIS ROUTINE FIGURES THE CHECKSUM FOR THE VOICES
CHECKSUM:      MOV     CX,4096 ;NUMBER OF BYTES IN DUMP
       SUB     AX,AX   ;NULL ACCUMULATOR
       MOV     SI,OFFSET CODE_END+6    ;POINT TO FIRST VOICE
CHECKSUM1:     ADD     AL,[SI] ;ADD BYTE
       INC     SI      ;BUMP POINTER
       LOOP    CHECKSUM1       ;AND LOOP
       NOT     AL      ;DO TWOS COMPLIMENT
       ADD     AL,1
       AND     AL,7FH  ;...SORT OF
       MOV     AH,[SI] ;GET CHECKSUM FROM MEMORY
       CMP     AH,AL   ;SET FLAG
       RET

;THIS ROUTINE SHOWS THE VOICE NAMES
SEE_DX:MOV     BX,OFFSET CODE_END+6    ;POINT TO FIRST VOICE
       MOV     DH,4    ;SET VERTICAL LINE
SEE_DX1:       MOV     DL,28   ;SET LEFT VOICE POSITION
       CALL    GOTOXY  ;MOVE CURSOR
       CALL    SEE_NAME;SHOW NAME
       ADD     BX,80H  ;POINT TO NEXT VOICE
       MOV     DL,42   ;SET RIGHT VOICE POSITION
       CALL    GOTOXY  ;MOVE CURSOR
       CALL    SEE_NAME;SHOW NAME
       ADD     BX,80H  ;POINT TO NEXT VOICE
       INC     DH      ;NEW LINE
       CMP     DH,20   ;SEE IF WE'RE DONE
       JL      SEE_DX1
       CALL    BLANK   ;SAY WE' DONE
       CALL    ILPRT
       DB      'Zap any key to continue',0
       CALL    GETCH
       RET

;THIS ROUTINE SHOWS ONE VOICE NAME
SEE_NAME:      PUSH    BX      ;SAVE POINTER
       ADD     BX,76H  ;POINT PAST OPERATORS 
       MOV     CX,10   ;TEN BYTES IN A VOICE
SEE_NAME1:     MOV     AL,[BX] ;GET A BYTE
       CALL    PUTCH   ;POINT IT
       INC     BX      ;BUMP POINTER
       LOOP    SEE_NAME1       ;AND LOOP
       POP     BX      ;RESTORE POINTER
       RET

;THIS ROUTINE CLEARS THE MESSAGE LINE
BLANK: MOV     DX,1800H;POSITION AT BOTTOM
       CALL    GOTOXY  ;...OF TUBE
       MOV     CX,79   ;AND PRINT
       MOV     AL,32   ;...A LINE OF SPACES
BLANK1:CALL    PUTCH
       LOOP    BLANK1
       MOV     DX,1800H;POSITION CURSOR AGAIN
       CALL    GOTOXY
       RET

;THIS ROUTINE GETS ONE BYTE FROM THE MIDI PORT
GET_MIDI:      SUB     CX,CX   ;RESET TIMEOUT COUNTER
GET_MIDI1:     MOV     DX,STATUS_PORT  ;GET STATUS PORT VALUE
       IN      AL,DX
       AND     AL,SEND_READY   ;SEE IF DATA'S A'WAITIN'
       CMP     AL,SEND_READY
       JNE     GET_MIDI2       ;LOOP 'TIL IT IS
       LOOP    GET_MIDI1
GET_MIDI2:     MOV     DX,DATA_PORT    ;WRANGLE THAT THAR DATA
       IN      AL,DX
       CMP     AL,0FEH ;IGNORE TIMERS
       JE      GET_MIDI
       RET

;THIS ROUTINE SENDS THE BYTE IN AL TO THE MIDI PORT
PUT_MIDI:      PUSH    AX      ;SAVE THE BYTE
PUT_MIDI1:     MOV     DX,STATUS_PORT  ;WAIT FOR CLEAR
       IN      AL,DX   ;... SIGNAL FROM BUS
       AND     AL,DATA_READY
       CMP     AL,DATA_READY
       JE      PUT_MIDI1
       POP     AX      ;RESTORE THE BYTE
       MOV     DX,DATA_PORT    ;AND SEND IT OUT
       OUT     DX,AL
       IN      AL,DX   ;I DON'T KNOW WHY IT
       RET     ;... WANTS THIS BUT IT DOES

;THIS ROUTINE SENDS A COMMAND TO THE MPU-401
PUT_MPU:       PUSH    AX      ;SAVE COMMAND
       SUB     CX,CX   ;RESET TIMEOUT COUNTER
PUT_MPU1:      MOV     DX,STATUS_PORT  ;WAIT FOR FREE PORT
       IN      AL,DX
       AND     AL,DATA_READY
       CMP     AL,DATA_READY
       JNE     PUT_MPU2
       LOOP    PUT_MPU1
PUT_MPU2:      POP     AX      ;RESTORE BYTE
       MOV     DX,COMMAND_PORT ;AND SEND IT
       OUT     DX,AL

       SUB     CX,CX   ;RESET TIMOUT COUNTER
PUT_MPU3:      MOV     DX,STATUS_PORT  ;WAIT FOR BYTE TO COME BACK
       IN      AL,DX
       AND     AL,SEND_READY
       CMP     AL,SEND_READY
       JNE     PUT_MPU4
       LOOP    PUT_MPU3
PUT_MPU4:      MOV     DX,DATA_PORT    ;GET THE BYTE
       IN      AL,DX
       CMP     AL,0FEH ;CHECK FOR ACK
       RET

;THIS ROUTINE GETS THE VERSION AND REVISION NUMBERS OF THE MPU-401
GET_VERSION:   MOV     AL,MPU_VERSION
       CALL    PUT_MPU ;ASK FOR VERSION
       CALL    GET_MIDI;GET THE BYTE
       
       PUSH    AX      ;SAVE IT
       AND     AL,0F0H ;ISOLATE HIGH NYBBLE
       MOV     CL,4
       SHR     AL,CL
       ADD     AL,'0'  ;ADD ASCII
       CALL    PUTCH   ;AND SHOW IT
       MOV     AL,'.'  ;PRINT A DOT
       CALL    PUTCH
       POP     AX      ;RESTORE BYTE
       AND     AL,0FH  ;ISOLATE LOW NYBBLE
       ADD     AL,'0'  ;ADD ASCII
       CALL    PUTCH   ;AND SHOW IT
       
       MOV     AL,MPU_REVISION ;ASK FOR REVISION
       CALL    PUT_MPU
       CALL    GET_MIDI;GET BYTE
       ADD     AL,64   ;ADD ASCII
       CALL    PUTCH   ;AND SHOW IT
       RET

;THIS ROUTINE TRASNLATES AL TO UPPER CASE
TOUPPER:       CMP     AL,'a'
       JL      TOUPPER1
       CMP     AL,'z'
       JG      TOUPPER1
       SUB     AL,'a'-'A'
TOUPPER1:      RET

;THIS ROUTINE DOES IN LINE PRINTING
ILPRT: POP     BX
ILPLP: MOV     AL,[BX]
       CMP     AL,0
       JE      ILPRET
       CALL    PUTCH
       INC     BX
       JMP     ILPLP
ILPRET:INC     BX
       PUSH    BX
       RET

;THIS ROUTINE PRINTS ONE CHARACTER
PUTCH: PUSH    DX
       PUSH    CX
       PUSH    BX
       PUSH    AX
       MOV     AH,15
       INT     10H
       POP     AX
       PUSH    AX
       MOV     AH,14
       INT     10H
       POP     AX
       POP     BX
       POP     CX
       POP     DX
       RET

;THIS ROUTINE GETS ONE CHARACTER FROM THE KEYBOARD
GETCH: PUSH    BX
       PUSH    CX
       PUSH    DX
       MOV     AH,0
       INT     16H
       POP     DX
       POP     CX
       POP     BX
       RET

;THIS ROUTINE POSITIONS THE CURSOR AT (DL,DH)
GOTOXY:PUSH    AX
       PUSH    BX
       PUSH    CX
       PUSH    DX
       MOV     AH,15
       INT     10H
       POP     DX
       PUSH    DX
       MOV     AH,2
       INT     10H
       POP     DX
       POP     CX
       POP     BX
       POP     AX
       RET

CLRSCRN:       MOV     AX,0600H
       MOV     BX,0700H
       MOV     CX,0000H
       MOV     DX,184FH
       INT     10H
       SUB     DX,DX
       CALL    GOTOXY
       RET

FILE_HANDLE:   DW      0
HEADER: DB      'DX-Archive Voice File '
       DB      'Copyright (c) 1985 Steve Rimmer '
CODE_END:      DB      0

STUFF  ENDP
CODEX  ENDS
       END     START





