; MINT.ASM  Version 1.0
; (c) 1990 Eletech Electronics, Inc. All rights UNreserved.
;
; Creation : 3-13-90 by Wei Lu.
; Function : Works with MIMIC.C to play Eletech Digicorder recorded voice
;            file thru PC's internal speaker at specified sampling rate. 
; Usage    : See MIMIC.C.
; Assembler: Microsoft MASM 5.0 and up.
;            Assembling Options: None.
;
; Operation: MINT.ASM is an interrupt handler. After MIMIC.C re-directs clock
;            interrupt, subsequent timer 0 interrupts are intercepted and
;            handled by MINT.ASM. 
;
;            MINT.ASM has two responsibilities:
;
;            a. Output voice data bit by bit to PC's speaker upon each timer
;               0 interrupt.
;
;            b. Maintain DOS clock operation by doing "real" clock interrupts
;               18.2 times per second.
;
;            MINT.ASM assumes that MIMIC.C has done the following setup:
;
;            a. Load voice data into play buffer pointed to by play_addr.
;
;            b. Calculate and set play_ptr, play_length and tack_count (see
;               MIMIC.C for explanations.)
;
;            c. Re-direct timer 0 (clock) interrupt and re-program timer 0 
;               interrupt rate to equal sampling rate.
;
;            Upon each timer 0 interrupt, MINT.ASM will be activated and do
;            one of the following:
;
;            a. If there is no more data to play, do a "real" clock interrupt
;               if it's time to, then terminate.
;
;            Otherwise,
;
;            b. If it does not need a new byte of data, output a bit and
;               terminate.
;
;            c. If it needs a new byte of data but it's not time yet to do a
;               "real" clock interrupt, get a byte, output a bit and 
;               terminate.
;
;            d. If it needs a new byte of data and it's also time to do a
;               "real" clock interrupt, get a byte, output a bit, do a "real"
;               clock interrupt and terminate.
;
;            The following codes are written so as to minimize the execution
;            time of case b above. See MIMIC.C for more operation details.

.model small

public        _int_handler
public        _play_addr
public        _play_ptr
public        _play_length
public        _tack_count

speaker       equ    97                     ; speaker I/O port
ocw2          equ    20h                    ; 8259 control I/O port
eoi           equ    20h                    ; "End Of Interrupt" control word
clock_irq     equ    255                    ; "real" clock new interrupt #

.data

_play_addr    label  dword                  ; play buffer is 64K bytes
_play_ptr     label  word                   ; actually the offset of play_addr
play_addr     label  dword                  ;   (set and changed by MIMIC.C)
play_addr_off dw     ?
play_addr_seg dw     ?                      ; does not change once set

_play_length  label  word                   ; number of bytes to play
play_length   dw     ?

_tack_count   label  byte                   ; see MIMIC.C
tack_count    db     ?

data_byte     db     55                     ; a byte of garbage doesn't hurt
bit_ptr       db     80h                    ; bits go from left to right
t_count       db     ?                      ; a working copy of tack_count

.code

_int_handler  proc   far
              push   ax
              push   ds

              mov    ax,@data
              mov    ds,ax
              cmp    play_length,0          ; are we out of voice data?
              je     no_more
              mov    al,data_byte
              mov    ah,bit_ptr             ; bit_ptr used as a mask
              and    al,ah                  ; is this bit 0 or 1?
              jz     zero
one:
              nop                           ; so that "one" and "zero" take
              nop                           ; the same amount of time to do
              mov    al,01001010b           ;   (optimized for AT)
              out    speaker,al
              ror    ah,1                   ; will set Carry if rotate around
              mov    bit_ptr,ah             ; update bit_ptr
              jc     adjust
              mov    al,eoi
              out    ocw2,al                ; re-enable interrupt requests
              pop    ds
              pop    ax
              iret
zero:
              mov    al,01001000b
              out    speaker,al
              ror    ah,1
              mov    bit_ptr,ah
              jc     adjust
              mov    al,eoi
              out    ocw2,al
              pop    ds
              pop    ax
              iret
adjust:                                     ; now we have to get a new byte
              sub    play_length,1          ; also update play_length
              push   si
              push   es
              les    si,dword ptr ds:play_addr
              mov    al,byte ptr es:[si]
              mov    data_byte,al           ; the new byte is in
              inc    si
              mov    play_addr_off,si       ; update play_ptr
              dec    t_count                ; is it time to do clock int?
              jz     do_interrupt
              mov    al,eoi
              out    ocw2,al
              pop    es
              pop    si
              pop    ds
              pop    ax
              iret
do_interrupt:
              mov    al,tack_count
              mov    t_count,al             ; reset t_count to tack_count
              int    clock_irq              ; do clock interrupt
              pop    es                     ; no need to re-enable interrupt
              pop    si                     ;   requests since it's already
              pop    ds                     ;   been done in clock interrupt
              pop    ax                     ;   service routine
              iret
no_more:
              ror    bit_ptr,1              ; still have to know when to do 
              jc     adj                    ;   clock interrupt
              mov    al,eoi
              out    ocw2,al
              pop    ds
              pop    ax
              iret
adj:
              dec    t_count
              jz     do_int
              mov    al,eoi
              out    ocw2,al
              pop    ds
              pop    ax
              iret
do_int:
              mov    al,tack_count
              mov    t_count,al
              int    clock_irq
              pop    ds
              pop    ax
              iret

_int_handler  endp

              end
