;; ------------------------------------------------------------------------ ;;
;;  @@ Source Documentation                 *** MASM 6.0 Version ***        ;;
;;                                                                          ;;
;;  Title : CTMIDILL.ASM                                                    ;;
;;                                                                          ;;
;;  Description :                                                           ;;
;;      CTMIDILL.ASM demonstrate MIDI IN and MIDI OUT by programming        ;;
;;      the MIDI PORT I/O directly. This program reads in MIDI data         ;;
;;      from external MIDI keyboard and send it back to the external        ;;
;;      synthesizer. At the same time the input MIDI data is displayed      ;;
;;      to the screen.                                                      ;;
;;                                                                          ;;
;;  Note :                                                                  ;;
;;      This sample does not serve as an application program. It is         ;;
;;      programmed under UART mode, and interrupt mode for in-bound data.   ;;
;;      The hardware settings are hardcoded as below :-                     ;;
;;          Base I/O address          : 220H                                ;;
;;          Interrupt                 : 5                                   ;;
;;          MPU-401 Base I/O address  : 330H   (for SB16 card only)         ;;
;;      There will be no messages displayed during the execution of this    ;;
;;      program. To terminate, simply press a key.                          ;;
;;                                                                          ;;
;;  Compile options :                                                       ;;
;;      /DNOLOOP - to disable MIDI input data loops back to external        ;;
;;                 synthesizer.                                             ;;
;;      /DMPU401 - if you are using Sound Blaster 16 card.                  ;;
;;                                                                          ;;
;;  Copyright (c) Creative Technology Ltd, 1993.  All rights reserved.      ;;
;;                                                                          ;;
;; ------------------------------------------------------------------------ ;;


;;; hardware setting

PORT_MPU401     EQU     330H        ; SB16 MIDI base i/o address
PORT_SBMIDI     EQU     220H
IRQ_LEVEL       EQU     5

        .MODEL  small
        .DOSSEG

        .STACK

        .DATA

    wCtIntNum               DW  5
    bSBC_INT_MASK_DATA      DB  ?
    wIntMaskPort            DW  ?
    wSoftwareInt            DW  ?
    bIntMaskBit             DB  ?
    ORG_INT_OFS             DW  ?
    ORG_INT_SEG             DW  ?

IFDEF   MPU401
    wBaseAddress    DW  PORT_MPU401
ELSE
    wBaseAddress    DW  PORT_SBMIDI
ENDIF


        .CODE
main    PROC
        .STARTUP

    IFNDEF   MPU401
        call    ResetSBMIDI
    ELSE
        call    ResetMPU401
    ENDIF

        test    ax,0001H
        jnz     FINIS

        call    ConfigureInterrupt

        ; -- setup interrupt service routine

        cli
        mov     bx,wSoftwareInt             ; preserve original ISR
        call    GetIntVector                ;
        mov     ORG_INT_OFS,AX              ;
        mov     ORG_INT_SEG,DX              ;

        mov     bx,wSoftwareInt             ;setup new ISR
        mov     dx,cs                       ;
        mov     ax,offset MidiISR           ;
        call    SetIntVector                ;

        ; -- enable interrupt mask

        mov     dx,wIntMaskPort
        in      al,dx                           ;preserve it
        mov     byte ptr bSBC_INT_MASK_DATA,al
        mov     ah,byte ptr bIntMaskBit
        not     ah
        and     al,ah
        out     dx,al

        ; -- start input

    IFDEF   MPU401
        call    EnterUARTModeMPU401
    ELSE
        call    EnterUARTModeSBMIDI
    ENDIF
        test    ax,0001H
        jnz     FINIS
        sti

        ; -- loop until user press a key

WAIT_UNTIL_KEY_PRESSED:
        mov     ah,01H                              ;get keyboard buffer status
        int     16H
        jz      WAIT_UNTIL_KEY_PRESSED

        ; restore for interrupt

        mov     al,byte ptr bSBC_INT_MASK_DATA      ; mask off the int
        mov     dx,wIntMaskPort
        out     dx,al

        mov     bx,wSoftwareInt
        mov     dx,ORG_INT_SEG
        mov     ax,ORG_INT_OFS
        call    SetIntVector

    IFDEF   MPU401
        call    ResetMPU401
    ELSE
        call    ResetSBMIDI
    ENDIF

FINIS:
        .EXIT
main    ENDP



;;-----------------------------------------------------------------------
;;  ResetMPU401
;;      Reset MPU-401 (for SB16 only)
;;  Entry   :
;;      none.
;;  Exit    :
;;      AX = 0 if successful else 1.
;;-----------------------------------------------------------------------

ResetMPU401     PROC    NEAR

        mov     dx,wBaseAddress     ;move base i/o addrss
        inc     dx                  ;status port
Busy1:
        in      al,dx               ;read status port
        test    al,40h              ;ready for output ?
        jnz     Busy1               ;Nope

        mov     al,0FFh             ;output reset command
        out     dx,al               ;       via command port

        sub     cx,cx               ;maximum of 65536 tries
Empty1:
        in      al,dx               ;read status port
        test    al,80h              ;input data ready ?
        jnz     NesLoop1            ;Nope

        dec     dx                  ;data port
        in      al,dx               ;read data
        cmp     al,0FEh             ;successfully reset ?
        mov     ax,00h
        je      ResetOK1            ;success !
        inc     dx                  ;status port

NesLoop1:
        loop    Empty1              ;try again
        mov     ax,01H              ;failed

ResetOK1:
        ret

ResetMPU401     ENDP



;;-----------------------------------------------------------------------
;;  EnterUARTModeMPU401
;;      Set MPU-401 to UART mode.
;;  Entry   :
;;      none.
;;  Exit    :
;;      AX = 0 if successful else 1.
;;-----------------------------------------------------------------------

EnterUARTModeMPU401     PROC    NEAR

        mov     dx,wBaseAddress     ;base i/o address
        inc     dx                  ;status port
Busy2:
        in      al,dx               ;read status port
        test    al,40h              ;ready for output
        jnz     Busy2

        mov     al,3Fh              ;output "Enter UART mode"
        out     dx,al               ;       command via command port

        sub     cx,cx               ;maximum of 65536 tries

Empty2:
        in      al,dx               ;read status port
        test    al,80h              ;input data ready ?
        jnz     NesLoop2            ;Nope

        dec     dx                  ;data port
        in      al,dx               ;read data
        cmp     al,0FEh             ;successful mode switch ?
        mov     ax,00h
        je      InUartMode2         ;success !
        inc     dx                  ;status port

NesLoop2:
        loop    Empty2              ;try again
        mov     ax,01h              ;failed

InUartMode2:
        ret

EnterUARTModeMPU401     ENDP



;;-----------------------------------------------------------------------
;;  WriteMPU401
;;      Write a byte of MIDI data to MPU-401
;;  Entry :
;;      al      = output data.
;;  Exit :
;;      none.
;;-----------------------------------------------------------------------

WriteMPU401     PROC    NEAR

        push    dx

        mov     dx,wBaseAddress     ;base i/o address
        inc     dx                  ;status port
        push    ax
Busy3:
        in      al,dx               ;read status port
        test    al,40h              ;ready for output ?
        jnz     Busy3               ;Nope

        dec     dx                  ;data port
        pop     ax                  ;output data
        out     dx,al               ;       via data port

        pop     dx
        ret

WriteMPU401     ENDP



;;-----------------------------------------------------------------------
;;  ReadMPU401
;;      Read a byte of MIDI data from MPU-401
;;  Entry :
;;      none.
;;  Exit :
;;      al      = input data.
;;-----------------------------------------------------------------------

ReadMPU401      PROC    NEAR

        push    dx

        mov     dx,wBaseAddress     ;base i/o address
        inc     dx                  ;status port
Busy4:
        in      al,dx               ;read status port
        test    al,80H              ;input data available ?
        jnz     Busy4

        dec     dx                  ;data port
        in      al,dx               ;input data via data port

        pop     dx
        ret

ReadMPU401      ENDP



;;-----------------------------------------------------------------------
;;  WriteSBMIDI
;;      Write command/data to DSP
;;  Entry :
;;      al      = output data.
;;  Exit :
;;      none.
;;-----------------------------------------------------------------------

WriteSBMIDI     PROC    NEAR

        push    dx

        mov     dx,wBaseAddress     ; base i/o address
        add     dl,0CH              ; WRITE BUFFER status port 2xCH
                                    ;       command or
        push    ax
WRITE11:
        in      al,dx
        test    al,80H              ; ready to receive cmomand/data ?
        jnz     WRITE11

        pop     ax                  ;output data
        out     dx,al               ;       via data port

        pop     dx
        ret

WriteSBMIDI     ENDP



;;-----------------------------------------------------------------------
;;  ReadSBMIDI
;;      Read data from DSP
;;  Entry :
;;      none.
;;  Exit :
;;      al = input data.
;;-----------------------------------------------------------------------

ReadSBMIDI      PROC    NEAR

        push    dx

        mov     dx,wBaseAddress     ; base i/o address
        add     dl,0EH              ; data avalailable port

READ11:
        in      al,dx
        test    al,80H              ; data available ?
        jz      READ11

        sub     dl,4                ; read data via READ DATA port
        in      al,dx               ;       2xAH

        pop     dx
        ret

ReadSBMIDI      ENDP


;;-----------------------------------------------------------------------
;;  EnterUARTModeSBMIDI
;;      Set SBMIDI to UART mode
;;  Entry :
;;      none
;;  Exit:
;;      ax      = 0
;;-----------------------------------------------------------------------

EnterUARTModeSBMIDI     PROC    NEAR

        mov     al,35H              ; enter UART mode command
        call    WriteSBMIDI         ; output command
        mov     ax,00

        ret

EnterUARTModeSBMIDI     ENDP



;;-----------------------------------------------------------------------
;;  ResetSBMIDI
;;      reset the SBMIDI
;;  Entry :
;;      none
;;  Exit:
;;      AX = 0 if successful else AX = 1.
;;-----------------------------------------------------------------------

ResetSBMIDI     PROC    NEAR

        push    cx

        mov     dx,wBaseAddress     ; base i/o addx
        add     dl,06H              ; reset port address
                                    ;       2x6
        mov     al,1                ; reset the card by
        out     dx,al               ;       output a 1 and followed by
                                    ;       a zero
        sub     al,al

RESET10:
        dec     al                  ; delay at least 3 usec
        jnz     RESET10

        out     dx,al               ; ...

        mov     cx,100              ; attempt 100 times
        add     dl,8                ; data ready port 2xE

RESET20:
        in      al,dx
        test    al,80H              ; data available ?
        jz      RESET20             ; loop

        sub     dl,4                ; read data port 2xA
        in      al,dx
        cmp     al,0AAH             ; reset sucessful ID 0AAH
        jne     RESET30             ; ne, not as expected

        sub     ax,ax
        jmp     short RESET90

RESET30:
        add     dl,4                ; data ready port 2xE
        loop    RESET20
        mov     ax,1

RESET90:
        pop     cx
        ret

ResetSBMIDI     ENDP



;;-----------------------------------------------------------------------
;;  DisplayHex
;;      Display a byte of hexadecimal value to the screen.
;;  Entry :
;;      al      = byte to be displayed.
;;  Exit:
;;      none.
;;-----------------------------------------------------------------------

DisplayHex      PROC    NEAR

        push    ax
        push    bx
        push    dx

        mov     dh,0
        mov     dl,al
        push    dx
        shr     dx,1                ;shift high nibble to lsb
        shr     dx,1
        shr     dx,1
        shr     dx,1
        and     dx,0FH

        clc                         ;clear carry flag
        cmp     dl,9
        jg      DISPH10

        add     dl,'0'
        jmp     DISPH20

DISPH10:
        sub     dl,0AH
        add     dl,'A'

DISPH20:
        mov     al,dl
        mov     ah,0EH
        mov     bl,7
        int     10H

        pop     dx
        and     dx,0FH

        clc                         ;clear carry flag
        cmp     dl,9
        jg      DISPH30

        add     dl,'0'
        jmp     DISPH40

DISPH30:
        sub     dl,0AH
        add     dl,'A'

DISPH40:
        mov     al,dl
        mov     ah,0EH
        mov     bl,7
        int     10H

        pop     dx
        pop     bx
        pop     ax
        ret

DisplayHex      ENDP



;;-----------------------------------------------------------------------
;;  MidiISR
;;      MIDI input Interrupt Service Routine.
;;  Entry :
;;      none.
;;  Exit:
;;      none.
;;-----------------------------------------------------------------------

MidiISR     PROC    NEAR

        push    ax
        push    bx
        push    cx
        push    dx
        push    si
        push    ds
        pushf

        mov     ax,@data
        mov     ds,ax

        mov     dx,wBaseAddress     ;base i/o address

IFDEF   MPU401                      ;MPU-401 mode
AGAIN:
        inc     dx                  ;status port
        in      al,dx               ;read status port
        test    al,80H              ;input data available ?
        jnz     NO_DATA             ;no more data

        dec     dx                  ;data port
        in      al,dx               ;input data via data port
    IFNDEF  NOLOOP
        call    WriteMPU401         ;output midi code
    ENDIF
ELSE                                ;SBMIDI mode
AGAIN:
        add     dl,0EH              ;data avalailable port
        in      al,dx
        test    al,80H              ;data available ?
        jz      NO_DATA             ;no more data

        sub     dl,4                ;read data via READ DATA port
        in      al,dx               ;       2xAH
    IFNDEF  NOLOOP
        call    WriteSBMIDI
    ENDIF
        sub     dl,0AH
ENDIF
        call    DisplayHex          ;display MIDI code
        mov     ah,0EH
        mov     al,' '
        mov     bl,7
        int     10H
        jmp     AGAIN

NO_DATA:
        mov     al,20H
        cmp     word ptr ds:wCtIntNum,8     ; high interrupt ?
        jb      II90                        ; no, skip next

        out     0A0H,al                     ; yes, acknowledge slave controller
II90:
        out     20H,al                      ; acknowledge master controller

        popf
        pop     ds
        pop     si
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        iret

MidiISR     ENDP



;;-----------------------------------------------------------------------
;;  ConfigureInterrupt
;;      Configure interrupt parameters base on wCtIntNum.
;;  Entry :
;;      none.
;;  Exit:
;;      none.
;;-----------------------------------------------------------------------

ConfigureInterrupt  PROC    NEAR

        push    ax
        push    cx

        mov     ax,wCtIntNum
        and     al,0FH

        mov     cx,ax

        and     cl,7H
        mov     ch,1
        shl     ch,cl
        mov     byte ptr bIntMaskBit,ch

        mov     cl,al
        add     cl,8
        cmp     cl,10H
        jb      ASI10

        add     cl,60H

ASI10:
        sub     ch,ch
        mov     wSoftwareInt,cx
        and     al,8
        cbw
        mov     cl,4
        shl     ax,cl
        add     ax,21H
        mov     wIntMaskPort,ax

        pop     cx
        pop     ax

        ret

ConfigureInterrupt  ENDP



;;-----------------------------------------------------------------------
;;  SetIntVector
;;      Set interrupt vector.
;;  Entry :
;;      bx      = vector number.
;;      dx:ax   = new vector.
;;  Exit:
;;      none.
;;-----------------------------------------------------------------------

SetIntVector    PROC    NEAR

        pushf
        push    ds

        shl     bx,1
        shl     bx,1

        cli

        push    ax
        sub     ax,ax
        mov     ds,ax
        pop     ax

        mov     [bx],ax
        mov     [bx+2],dx

        pop     ds
        popf

        ret

SetIntVector    ENDP


;;-----------------------------------------------------------------------
;;  GetIntVector
;;      Get interrupt vector.
;;  Entry :
;;      bx      = vector number.
;;  Exit:
;;      dx:ax   = current vector.
;;-----------------------------------------------------------------------

GetIntVector    PROC    NEAR

    pushf
    push    ds

    shl     bx,1                        ; 4 byte per vector
    shl     bx,1

    cli
    sub     ax,ax                       ; vector table at segment 0
    mov     ds,ax

    mov     ax,[bx]
    mov     dx,[bx+2]

    pop     ds
    popf

    ret

GetIntVector    ENDP

main_end:
        END         ;;; -- End of file -- ;;;
