        TITLE   MPU/MIDI primitives for Modular Sequencer
        NAME    MBM
        .SALL
;==============================================================
; MusicBox Modular Sequencer, Version 2
; midi and clock interface
;--------------------------------------------------------------
; author: John Dunn
; date:   03/07/86
; update: 03/20/88
;--------------------------------------------------------------
; COPYRIGHT (C) 1986 John Dunn, All Rights Reserved 
; Entered into the Public Domain, March 20, 1988
;
; Use and copying of this software and preparation of derivative works
; based upon this software are permitted.  Any distribution of this
; software or derivative works must comply with all applicable United
; States export control laws.
; 
; This software is made available AS IS, and the author makes no warranty 
; about the software, its performance, or its conformity to any specification.
; 
; Any person obtaining a copy of this software is requested to send their
; name and address address to:
;
;       John Dunn, Senior Research Fellow
;       Time Arts Inc.
;       3436 Mendocino Ave.
;       Santa Rosa, CA 95401
;
;==============================================================
        include	order.asm
;--------------------------------------------------------------
        include equates.asm
;==============================================================
_DATA   SEGMENT
        ASSUME DS:DGROUP, CS:_TEXT
;--------------------------------------------------------------
        public  midip
;--------------------------------------------------------------
        extrn   midiok:byte
;--------------------------------------------------------------
moboix0 dw      0               ; MIDI Out Buffer Index
mobiix0 dw      0               ; MIDI In Buffer Index
;--------------------------------------------------------------
moboix1 dw      0               ; MIDI Out Buffer Index
mobiix1 dw      0               ; MIDI In Buffer Index
;--------------------------------------------------------------
midip   db      0               ; midi port number 0/1
mpuis   db      0               ; nz if mpu intes happening
savint0 db      0               ; saved int byte from 8259A
;--------------------------------------------------------------
_DATA   ENDS
;==============================================================
_TEXT   SEGMENT
	ASSUME  CS: _TEXT, DS: DGROUP, SS: DGROUP, ES: NOTHING
;==============================================================
        extrn   _dummy:far
        extrn   loops:word
;==============================================================
; basic midi port routines
;--------------------------------------------------------------
; words declared in the CS for faster interrupt service  
        public  midixs,mstop,mstart,mcont,midata,miflag,midatix,misend
        public  _mpuinf,_mpuinm
;--------------------------------------------------------------
midixs  db      0               ; midi extrnal sync tick
mstop   db      0               ; midi stop tick
mstart  db      0               ; midi start tick
mcont   db      0               ; midi continue tick
midata  db      0               ; midi input data byte
miflag  db      0               ; midi input data flag
misysx  db      0               ; midi sys exclusive flag
midatix db      0               ; midi data index
misend  db      0               ; midi send data flag
_mpuinf db      0               ; mpu input from 1=1, 2=2, 0=none
_mpuinm db      3               ; mpu input mask        
nottrue db      'install patch area',0,1,3,7,0
mobuf1  db      8192+105 dup(?) ; MIDI Out Buffer
mobuf0  db      8192+128 dup(?) ; MIDI Out Buffer
;--------------------------------------------------------------
; initialize mpu, int vectors, etc.
; must be called once only on startup
; sets direct MIDI mode, with interrupt on input
; sets interrupt vector 0AH at 0000:0028, for
; hardware interrupt 2.
;
        public  startm  
startm  proc    near
        test    mpuis,1         ; already done it?
        jnz     initmx          ; yes, split
        push    es              ; save current es
        mov     ah,35h          ; get current int 2
        mov     al,0AH
        int     21H
        mov     word ptr cs:orgint+3,es        ; save it
        mov     word ptr cs:orgint+1,bx
        pop     es              ; restore es

        push    ds              ; save current ds
        mov     ah,25H          ; set int vect
        mov     al,0AH          ; int 2 for MPU
        mov     dx,offset mpuint
        mov     cx,seg mpuint
        mov     ds,cx
        int     21H             ; set new int vect
        pop     ds              ; restore ds

        cli
        call    mpurst          ; reset mpu
        in      al,21H          ; enable irq2
        mov     savint0,al      ; save it
        and     al,0FBH
        out     21H,al
        mov     mpuis,1         ; flag = active
        sti
initmx: ret
startm  endp                     ; end of init mpu
;--------------------------------------------------------------
; shutdown mpu, restore int vectors, etc.
; must be called once only on exit
;
        public  stopm  
stopm   proc    near
        test    mpuis,1         ; already done it?
        jz      exitmx          ; yes, split
        cli
        mov     al,0FFH         ; issue mpu reset
        mov     dx,mpucmd
        out     dx,al
        add     dx,2            ; do 2nd port
        out     dx,al
        mov     al,savint0      ; get original irq masks
        out     21H,al          ; restore them
        mov     mpuis,0         ; flag = inactive
        ;
        push    ds
        mov     ah,25h          ; restore previous irq2
        mov     al,0AH
        mov     dx,word ptr cs:orgint+1
        mov     cx,word ptr cs:orgint+3
        mov     ds,cx
        int     21h
        pop     ds
        ;
exitmx: ret
stopm   endp

;--------------------------------------------------------------
; this routine is only called by int 2
; if it was not generated by the mpu, it vectors
; to the original int address, otherwise it saves
; registers, then calls the c routine _mpuint,
; after which it restores registers, clears nmi
; and returns from int. 
;
mpuint  proc    far
        cli                     ; disable interupts
        push    ax              ; save ax
        push    dx

        mov     ah,1            ; set mpu in flag to mpu#1
        mov     dx,mpstat       ; read mpu status
        in      al,dx
        and     al,mpdsr        ; generated by mpu?
        jz      mpui1           ; yes, branch
        add     dx,2            ; no, check other port
        mov     ah,2            ; set mpu in flag to mpu#1
        in      al,dx
        and     al,mpdsr        ; generated by mpu?
        jz      mpui1           ; yes, branch

        mov     ah,1            ; set mpu in flag to mpu#1
        mov     dx,mpstat       ; read mpu status
        in      al,dx
        and     al,mpdsr        ; generated by mpu?
        jz      mpui1           ; yes, branch
        add     dx,2            ; no, check other port
        mov     ah,2            ; set mpu in flag to mpu#1
        in      al,dx
        and     al,mpdsr        ; generated by mpu?
        jz      mpui1           ; yes, branch

        mov     ah,1            ; set mpu in flag to mpu#1
        mov     dx,mpstat       ; read mpu status
        in      al,dx
        and     al,mpdsr        ; generated by mpu?
        jz      mpui1           ; yes, branch
        add     dx,2            ; no, check other port
        mov     ah,2            ; set mpu in flag to mpu#1
        in      al,dx
        and     al,mpdsr        ; generated by mpu?
        jz      mpui1           ; yes, branch

        mov     al,20H
        out     20H,al

        pop     dx              ; restore cpu
        pop     ax
        sti                     ; enable interupts
        iret
orgint: jmp     far ptr _dummy  ; dummy vector, filled by _initm

mpui1:
        push    bx              ; save cpu state
        push    cx
        push    di
        push    si
        push    bp
        push    ds
        push    es

        mov     cx,cs           ; set up seg regs
        mov     ds,cx

        dec     dx              ; get mpu int data
        in      al,dx           ; /

        mov     _mpuinf,ah      ; save flag status
        test    ah,_mpuinm      ; check against mask
        jz      mpuiz           ; exit if masked out

        cmp     al,0feh         ; Active Sensing
        jz      mpuiz           ; yes, exit
        cmp     al,0f8H         ; Midi Sync?
        jnz     mpui2           ; no, branch
        inc     midixs          ; yes, inc the flag
        jmp     short mpuiz     ; .. and on to work
mpui2:  cmp     al,0fah         ; Midi Start?
        jnz     mpui3           ; no, branch
        inc     mstart          ; yes, inc the flag
        jmp     short mpuiz     ; .. and on to work
mpui3:  cmp     al,0fbh         ; Midi Continue
        jnz     mpui4           ; no, branch
        inc     mcont           ; yes, inc the flag
        jmp     short mpuiz     ; .. and on to work
mpui4:  cmp     al,0fch         ; Midi stop?
        jnz     mpui5           ; no, branch
        inc     mstop           ; yes, inc the flag
        jmp     short mpuiz     ; .. and on to work
mpui5:  mov     midata,al       ; put the data byte away 
        mov     miflag,1        ; set the data-in flag
        cmp     al,0f0h         ; system exclusive
        jnz     mpui6           ; no, branch
        mov     misysx,1        ; yes, set the flag
        jmp     short mpuiz     ; exit
mpui6:  cmp     al,0f7h         ; EOX?
        jnz     mpui7           ; no, branch
        mov     misysx,0        ; yes, clear the flag
        jmp     short mpuiz     ; exit
mpui7:  test    misysx,-1       ; sys exclusive happening?
        jnz     mpuiz           ; yes, branch
        test    misend,-1       ; want to send data?
        jz      mpui8           ; no, branch
        mov     dx,seg bufsp    ; yes get the buffer
        mov     es,dx           ; /
        mov     bl,midatix      ; get the index
        mov     bh,0FH          ; put in bank "F"
        mov     es:[bx],al      ; put the byte away
mpui8:  inc     midatix         ; inc the index
mpuiz:  ;
        pop     es              ; restore seg regs
        pop     ds
        pop     bp              ; restore cpu state
        pop     si
        pop     di
        pop     cx
        pop     bx
        pop     dx
        mov     al,20H
        out     20H,al
        pop     ax
        sti                     ; enable interrupts
        iret                    ; return from interrupt
mpuint  endp
;--------------------------------------------------------------
; mpurst
; clear out the mpu, and set for direct MIDI i/o
;
        public  mpurst
mpurst  proc    near
        cli                     ; disable intes
        mov     dx,mpdata       ; read mpu data port
        in      al,dx           ; get the data
        jmp     short $+2       ; wait
        in      al,dx           ; get the data
        jmp     short $+2       ; wait
        in      al,dx           ; get the data
        jmp     short $+2       ; wait
        in      al,dx           ; get the data
        jmp     short $+2       ; wait
        mov     dx,mpstat       ; mpu status port
mpurs1: in      al,dx           ; get the status
        and     al,mpdrr        ; test for Data Recieve Ready
        jnz     mpurs1          ; loop til ready
        mov     ax,03FH         ; MPU UART command
        out     dx,al           ; send to mpu
        mov     cx,800H         ; delay
mpurs2: loop    mpurs2          ; /
        mov     dx,mpdata       ; read mpu data port
        in      al,dx           ; get the data
        jmp     short $+2       ; wait
        in      al,dx           ; get the data
        ;
        ; do 2nd MPU
        ;
        test    midiok,2        ; 2nd MPU online?
        jz      mpursx          ; no, just exit
        ;
        mov     dx,mpdata+2     ; read mpu data port
        in      al,dx           ; get the data
        jmp     short $+2       ; wait
        in      al,dx           ; get the data
        jmp     short $+2       ; wait
        in      al,dx           ; get the data
        jmp     short $+2       ; wait
        in      al,dx           ; get the data
        jmp     short $+2       ; wait
        mov     dx,mpstat+2     ; mpu status port
mpurs3: in      al,dx           ; get the status
        and     al,mpdrr        ; test for Data Recieve Ready
        jnz     mpurs3          ; loop til ready
        mov     ax,03FH         ; MPU UART command
        out     dx,al           ; send to mpu
        mov     cx,800H         ; delay
mpurs4: loop    mpurs4          ; /
        mov     dx,mpdata+2     ; read mpu data port
        in      al,dx           ; get the data
        jmp     short $+2       ; wait
        in      al,dx           ; get the data
        ;
mpursx: sti                     ; enable interrupts
        ret                     ; exit
mpurst  endp
;--------------------------------------------------------------
; send a byte in AL to MIDI Out Buffer
;
        public  tomidi
tomidi  proc    near
        test    midip,10H       ; test midi port
        jnz     tomidi1         ; branch if port 1
        ;
        mov     bx,mobiix0      ; get midi out buffer out index
        mov     cs:mobuf0[bx],al; put next byte
        inc     bx              ; bump index
        and     bx,8191         ; wrap-around
        mov     mobiix0,bx      ; store new mob out index
        ret
        ;
tomidi1:mov     bx,mobiix1      ; get midi out buffer out index
        mov     cs:mobuf1[bx],al; put next byte
        inc     bx              ; bump index
        and     bx,8191         ; wrap-around
        mov     mobiix1,bx      ; store new mob out index
        ret
tomidi  endp
;--------------------------------------------------------------
; send a byte in AL to all MIDI Out Buffers
;
        public  allmidi
allmidi proc    near
        ;
        mov     bx,mobiix0      ; get midi out buffer out index
        mov     cs:mobuf0[bx],al; put next byte
        inc     bx              ; bump index
        and     bx,8191         ; wrap-around
        mov     mobiix0,bx      ; store new mob out index
        ;
        mov     bx,mobiix1      ; get midi out buffer out index
        mov     cs:mobuf1[bx],al; put next byte
        inc     bx              ; bump index
        and     bx,8191         ; wrap-around
        mov     mobiix1,bx      ; store new mob out index
        ret
allmidi endp
;--------------------------------------------------------------
; get next byte from pass buffer and send to midi
; does nothing if midi output port is busy  
; or if the buffer is empty
; 
        public  sendm
sendm   proc    near 
        mov     bx,moboix0      ; get midi out buffer out index
        cmp     bx,mobiix0      ; same as midi out buffer in index?
        jz      sendm1          ; yes, exit
        ;
        test    midiok,1        ; ok to do it?
        jz      sendm2          ; no, then fake it 
        ;
        mov     dx,mpstat       ; mpu status port
        in      al,dx           ; get the status
        and     al,mpdrr        ; test for Data Recieve Ready
        jnz     sendm1          ; exit if midi out port is busy
        ;
        mov     al,cs:mobuf0[bx]; get next byte
        mov     dx,mpdata       ; mpu data port
        out     dx,al           ; send to mpu
sendm2: inc     bx              ; bump index
        and     bx,8191         ; wrap-around
        mov     moboix0,bx      ; store new mob out index
        ;
        ; do 2nd buffer
        ;
sendm1: mov     bx,moboix1      ; get midi out buffer out index
        cmp     bx,mobiix1      ; same as midi out buffer in index?
        jz      sendm3          ; yes, exit
        ;
        test    midiok,2        ; ok to do it?
        jz      sendm4          ; no, then fake it 
        ;
        mov     dx,mpstat+2     ; mpu status port
        in      al,dx           ; get the status
        and     al,mpdrr        ; test for Data Recieve Ready
        jnz     sendm3          ; exit if midi out port is busy
        ;
        mov     al,cs:mobuf1[bx]; get next byte
        mov     dx,mpdata+2     ; mpu data port
        out     dx,al           ; send to mpu
sendm4: inc     bx              ; bump index
        and     bx,8191         ; wrap-around
        mov     moboix1,bx      ; store new mob out index
sendm3: ret
sendm   endp        
;--------------------------------------------------------------
; Clear all MIDI channels
;
        public  allclr
allclr  proc    near
allclr1:mov     midip,0         ; set midi port 0
        call    sendm           ; now really do it
allclr2:mov     midip,10H       ; set midi port 1
        call    sendm           ; now really do it
        mov     bx,moboix0      ; get midi out buffer out index
        cmp     bx,mobiix0      ; same as midi in buffer in index?
        jnz     allclr1         ; no, keep sending
        mov     bx,moboix1      ; get midi out buffer out index
        cmp     bx,mobiix1      ; same as midi in buffer in index?
        jnz     allclr2         ; no, keep sending
        ret
allclr  endp

;--------------------------------------------------------------
; test for midi output buffers empty
; returns al bits 0,1 set if buffers are not empty
;
        public  tstmob
tstmob: xor     al,al           ; clear flag
        mov     bx,moboix0      ; get midi out buffer out index
        cmp     bx,mobiix0      ; same as midi in buffer in index?
        jz      tstmob1         ; yes, branch
        inc     al              ; no, set the bit        
tstmob1:mov     bx,moboix1      ; get midi out buffer out index
        cmp     bx,mobiix1      ; same as midi in buffer in index?
        jz      tstmob2         ; yes, branch
        or      al,2            ; no, set the bit
tstmob2:ret                     ; exit w result in AL
;--------------------------------------------------------------
; Turn off all MIDI modules
;
        public  alloff
alloff  proc    near
        ;
        mov     dh,0            ; for channels
        mov     cl,dh           ; dummy data byte
workh1c:mov     ah,0B0H         ; MIDI Channel message
        or      ah,dh           ; add channel info
        ;
        mov     al,ah           ; midi channel msg
        call    allmidi         ; /
        mov     al,7CH          ; omni off 
        call    allmidi         ; send
        mov     al,cl           ; send dummy
        call    allmidi         ; /
        ;
        mov     al,ah           ; midi channel msg
        call    allmidi         ; /
        mov     al,7FH          ; poly, ano
        call    allmidi         ; /
        mov     al,cl           ; send dummy
        call    allmidi         ; /
        ;
        inc     dh              ; next channel
        test    dh,16           ; 16 channels
        jz      workh1c         ; /
        ;
        call    allclr          ; clear the channels
        ret
alloff  endp
;--------------------------------------------------------------
; TIMER ROUTINES
;--------------------------------------------------------------
; words declared in the CS for faster interrupt service  
        public  ticks,ticks1,timer,seconds,secondf
;--------------------------------------------------------------
timeis  db      0               ; nz if timer inte is happening
times0  dw      0               ; storage for dos timer vector
times1  dw      0               ; saa
ticks   dw      0               ; lsw of tick count
ticks1  dw      0               ; msw of tick count
timer   dw      0               ; system timer count
second0 dw      582             ; real time clk ticker
seconds dw      0               ; running seconds count
secondf db      0               ; nz if seconds was incremented
;--------------------------------------------------------------
; startt ( sets timer int to vector to dotime )
; 
        public  startt
startt  proc    near
        test    cs:timeis,1     ; already done it?
        jnz     starttx         ; yes, split
        push    es
        mov     ah,35H          ; get current int 8
        mov     al,8            ;   /
        int     21H             ; /
        mov     cs:times1,es    ; save it
        mov     cs:times0,bx
        pop     es
        ;
        push    ds              ; save data seg
        mov     ah,25H          ; set int vect
        mov     al,8            ; int 8 for MS timer
        mov     dx,offset dotime;
        mov     cx,seg dotime   ;
        mov     ds,cx           ;
        int     21H             ; set new int vect
        pop     ds              ; restore data seg
        mov     cs:timeis,1     ; flag = active
        ;
        cli
;        mov     ax,00fffh       ; set new timing
        mov     ax,007ffh       ; set new timing
        out     40H,al
        jmp     short $+2
        mov     al,ah
        out     40H,al
        sti
starttx:ret
startt  endp
;--------------------------------------------------------------
; stopt  ( restores oringinal dos int )
;
        public  stopt
stopt   proc    near
        test    cs:timeis,1     ; already done it?
        jz      stoptx          ; yes, split
        push    ds              ; save current data seg
        mov     ah,25h          ; restore previous irq
        mov     al,8            ;         /
        mov     dx,cs:times0    ;       /
        mov     cx,cs:times1    ;     /
        mov     ds,cx           ;   /
        int     21h             ; /
        pop     ds              ; restore data seg
        mov     cs:timeis,0     ; flag = inactive
        ;
        cli                     ; restore original timing
        mov     ax,0ffffh
        out     40H,al
        jmp     short $+2
        mov     al,ah
        out     40H,al
        sti
stoptx: ret
stopt   endp

;--------------------------------------------------------------
; DOTIME  interrupt routine called every clock tick
;
dotime  proc    far
        dec     cs:second0      ; count down second timer
        jnz     dotim2          ; branch if not time
        inc     cs:seconds      ; else bump seconds count
        mov     cs:second0,582  ; 582.4 ticks/sec
        mov     cs:secondf,1    ; set the flag
dotim2:
        test    cs:timer,-1     ; timer zero
        jz      dotim1          ; yes, branch
        dec     cs:timer        ; no, count down
dotim1: inc     cs:ticks        ; inc ls word
        jnz     dotim0          ; branch if not overflow
        inc     cs:ticks1       ; else inc ms word
;dotim0: test    cs:ticks,15     ; bump system stuff every 16th clock
dotim0: test    cs:ticks,31     ; bump system stuff every 32th clock
        jz      dotimx          ; branch if time to doit
        push    ax
        mov     al,20h
        out     20h,al
        pop     ax
        iret                    ; else just return from interrupt
dotimx: push    cs:times1       ; on to the original inte
        push    cs:times0       ;   /
        ret                     ; /
dotime  endp
;--------------------------------------------------------------
_TEXT   ENDS
        END

