;-------------------------------------------------------------------------------
;       NAME:  ADLIB.ASM $Revision: 1.7 $
;       COPYRIGHT:
;       "Copyright (c) 1994,1995 by e-Tek Labs"
; 
;        "This software is furnished under a license and may be used,
;        copied, or disclosed only in accordance with the terms of such
;        license and with the inclusion of the above copyright notice.
;        This software or any other copies thereof may not be provided or
;        otherwise made available to any other person. No title to and
;        ownership of the software is hereby transfered."
;-------------------------------------------------------------------------------
; Contains Assembly routines used for ADLIB emulation
; The only entry points include:
;    proc_2x9_write() - handles write to 2x9
;    reset_SB()       - resets interrupts associated with SB/ADLIB interrupt
;-------------------------------------------------------------------------------
.386

INCLUDE sbosdefs.inc

_DATA     segment word public use16 'DATA'
_DATA     ends
_BSS      segment word public use16 'BSS'
_BSS      ends
_TEXT     segment byte public use16 'CODE'
_TEXT     ends

DGROUP    group    _DATA,_BSS,_TEXT

_DATA     segment word public use16 'DATA'

;---------------------------------------------------------------------
; LIST OF PUBLICS
;---------------------------------------------------------------------
public proc_2x9_write
public reset_SB
public _op2chan
public _op2mod
public timer_prog

;---------------------------------------------------------------------
; LIST OF EXTERNS
;---------------------------------------------------------------------
;
; External C functions
;
extrn _set_global_ptrs:near
extrn _process_updates:near
;
; C variables
;
extrn _shared:word
extrn _fm_data:byte
extrn _fm_voice:word
extrn _fm_chan:word
extrn _things_to_do:word
;
; FM functions in sbc.c
;
extrn _fmC_parameter:near
extrn _fmC_ksl_atten:near
extrn _fmC_attack_decay:near
extrn _fmC_sust_rel:near
extrn _fmC_freqL:near
extrn _fmC_key_freqH:near
extrn _fmC_rhythm:near
extrn _fmC_con_feedback:near
extrn _fmC_wavesel:near
extrn _syn_delay:near
;
; All needed ports
;
extrn _reg_mixer:word
extrn _reg_index:word
extrn _reg_data_high:word
extrn _reg_388_read:word
extrn _reg_update_sb2xa:word
extrn _reg_389:word
extrn _adsbint_mask:byte
;
; Debugging data space
;
IFDEF DEBUG
extrn _add_to_debug_table:near
extrn _enable_debug:word
extrn _fmregs:byte
ENDIF
;
;  op2chan - Conversion table from adlib register offset to channel
;
;  0,1,2    - channel 0,1,2 modulator
;  3,4,5    - channel 0,1,2 carrier
;  6,7      - not used
;  8,9,10   - channel 3,4,5 modulator
;  11,12,13 - channel 3,4,5 carrier
;  14,15    - not used
;  16,17,18 - channel 6,7,8 modulator
;  19,20,21 - channel 6,7,8 carrier
;  
_op2chan db 0,1,2,0,1,2,0,0,3,4,5,3,4,5,0,0,6,7,8,6,7,8
;
;  op2mod - Conversion Long from adlib register offset to modulator
;  1 indicates modulator - see above chart
;
_op2mod dd 0001110000011100000111b
;
_DATA ends

_TEXT           segment byte public use16 'CODE'
assume          cs:DGROUP, ds:DGROUP, ss:DGROUP

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; set_388 - 
;
; DESCRIPTION:
;
; EXPECTS:
;
; MODIFIES:
;
; SEE ALSO:
;
; RETURNS:
;
;-------------------------------------------------------------------------------
set_388 proc near
    mov dx,388h
    mov al,4    ; so no NMI's ....
    out dx,al

    mov dx,389h
    mov al,0    ; toggle it low ...
    out dx,al

    mov al,80h    ; toggle it high will clear timer ....
    out dx,al

    ret
set_388 endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; proc_2x9_write - ADLIB compatability routine
;
; DESCRIPTION:
;   ADLIB compatability routine - reads 2x9,389 data and 2x8,388 index
;   and does a binary search to decode the register address.
;   All channel specific registers call decode_chan() and fm_voice is set
;   to a default of carrier.  All operator specific registers call 
;   decode_chan_voice() and fm_voice is set to carrier or modulator.  Both
;   of these routines call set_global_ptrs() which sets pointers into global
;   translation structures.  Any other registers call routines which don't 
;   care about fm_voice or channels.  Once the appropriate fm function is 
;   called, process_updates() is called to act on the changes made by the 
;   last register change.
;
; EXPECTS:
;   Expects the soundcard registers hold the values to be interpreted.
;
;-------------------------------------------------------------------------------
proc_2x9_write proc near
    ;
    ; Add the 388 write to debug table first so it prints out first
    ;
    mov dx,_reg_388_read ; Command register
    in  al,dx            ; get index
    mov bl,al            ; temp store of index
    xor bh,bh            ; zero the high byte of index
IFDEF D_AD
    mov dl,R388W_CODE
    call near ptr _add_to_debug_table
ENDIF
    mov dx,_reg_389      ; Data register
    in  al,dx            ; get data and clear IRQ (only for GF1)
    mov _fm_data,al      ; store data in global location
    call near ptr reset_SB ; For InterWave chip only ....
    ;
    ; DEBUG: store each ADLIB write into the fmregs table
    ;
IFDEF DEBUG
    mov byte ptr _fmregs[bx],al
ENDIF
IFDEF D_AD
    mov dl,R389W_CODE
    call near ptr _add_to_debug_table
ENDIF
    ;
    ; bl = register index
    ; Lets see how quick we can decode the register
    ; (we used to decode with lookup table but it was too big and too slow)
    ; First, lets split the register array in half (binary search)
    ;
    cmp bl,080H                ; if it's above 80, let's say it's in the top
    jae in_top_half
    cmp bl,020H
    jb short do_low_8
    ;
    ; Its between 20 and 7F
    ;
    cmp bl,40H
    jae short try_40H
    cmp bl,035H
    ja no_updates
    ;
    ;  ******  20-35  -  am-vib-eg-ksr-multiple
    ;
    mov ax,20h
    call near ptr decode_chan_voice
    call near ptr _fmC_parameter
    jmp check_updates
try_40H:
    cmp bl,60H
    jae short do_60
    cmp bl,055H
    ja no_updates
    ;
    ;  ******  40-55  -  ksl-total level
    ;
    mov ax,40h
    call near ptr decode_chan_voice
    call near ptr _fmC_ksl_atten
    jmp check_updates
do_60:
    cmp bl,075H
    ja no_updates
    ;
    ;  ******  60-75  -  Attack-Decay
    ;
    mov ax,60h
    call near ptr decode_chan_voice
    call near ptr _fmC_attack_decay
    jmp check_updates
do_low_8:
    ;
    ; It's in the first 8 registers. Just check them individually.
    ;
    cmp bl,02h
    jne short not_timer1
    ;
    ;  ******  02  -  Timer1
    ;
    mov al,46h              ; write to 3x3 to select Adlib timer 1
    jmp short timer_prog    ; write value to timer
not_timer1:
    cmp bl,03h
    jne short not_timer2
    ;
    ;  ******  03  -  Timer2
    ;
    mov al,47h              ; write to 3x3 to select Adlib timer 2
    ;
    ; Jumped from either ADLIB timer1 or timer2
    ; Sets IW timers for app
    ; EXPECTS: AL = 46h or 47h - timer1 or timer2 offset into register bank
    ;
timer_prog:
    mov bl,al                ; save the timer #
    mov dx,_reg_index
    out dx,al
    call near ptr _syn_delay
    mov dx,_reg_data_high
    mov al,_fm_data
    out dx,al
    ;
    ; Don't change this code. The F-GF1 doesn't seem to grab the timer
    ; value right away. This causes an unpredicatble time for the timers.
    ; (Thats not too cool .....)
    ;
;IFDEF NEVER
    push ax
    mov al,45h
    mov dx,_reg_index
    out dx,al
    mov dx,_reg_data_high
    ;
    mov al,_adsbint_mask
    or al,10h               ; enable timer interrupts
    ;mov al,32h
    ;vjf
    out dx,al
    call near ptr _syn_delay
    mov al,45h
    mov dx,_reg_index
    out dx,al
    mov dx,_reg_data_high
    mov al,_adsbint_mask
    out dx,al

    pop ax
    cmp al,0FFH
    jne no_waits
    cmp bl,46h                ; is it timer #1
    jne no_waits
    mov cx,100                ; make sure we don't wait forever ...
p2:
    mov dx,388H
    mov al,04h
    out dx,al
    inc dx
    mov al,21h
    out dx,al
still_waiting:
    mov dx,388H
    in al,dx
    and al,080H
    cmp al,080H
    je no_waits
    loop p2
    nop
no_waits:
;ENDIF
; Now clear the status register (388 .....)
;    call near ptr set_388

    jmp no_updates

not_timer2:
    ;
    ; Don't care what else it is. (0,1,4,5,6,7 or 8) Ignore it.
    ;
    cmp bl,04h
    je no_updates
    call near ptr set_388            ; set the status like the F-SB !@#$
    jmp no_updates
in_top_half:
    cmp bl,0bdh 
    ja short in_top_2
    jb short in_bottom_3
    ;
    ;  ******  0BD  -  Percussion register
    ;
    call near ptr _fmC_rhythm
    jmp short check_updates
in_top_2:
    cmp bl,0C0H
    jb short no_updates
    cmp bl,0E0h
    jb short do_C0
    cmp bl,0F5H
    ja short no_updates
    ;
    ;  ******  E0-F5  -  Wave select
    ;
    mov ax,0E0h
    call near ptr decode_chan_voice
    call near ptr _fmC_wavesel
    jmp short check_updates
do_C0:
    cmp bl,0C8h
    ja short no_updates
    ;
    ;  ******  C0-C8  -  Feed-back,connection
    ;
    mov ax,0C0h
    call near ptr decode_chan
    call near ptr _fmC_con_feedback
    jmp short check_updates
in_bottom_3:
    ;
    ; Just check sequentially.
    ;
    cmp bl,0A0H
    jae short try_B0
    cmp bl,095h
    ja short no_updates
    ;
    ;  ******  80-95  -  Sustain-Release
    ;
    mov ax,80h
    call near ptr decode_chan_voice
    call near ptr _fmC_sust_rel
    jmp short check_updates
try_B0:
    cmp bl,0B0H
    jae short do_B0
    cmp bl,0A8h
    ja short no_updates
    ;
    ;  ******  0A0H-0AFH  -  Frequency low
    ;
    mov ax,0A0h
    call near ptr decode_chan
    call near ptr _fmC_freqL
    jmp short check_updates
do_B0:
    cmp bl,0B8h
    ja short no_updates
    ;
    ;  ******  B0-B8  -  Keyon, Block, Fnum(High)
    ;
    mov ax,0B0h
    call near ptr decode_chan
    call near ptr _fmC_key_freqH
check_updates:
    cmp _things_to_do,0
    je short no_updates
    call near ptr _process_updates ; Handle anything that MAY have changed
no_updates:
    ret
proc_2x9_write endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; reset_SB - Resets chip after genaration of IRQ via Adlib/SoundBlaster ports
;
; DESCRIPTION:
;   Write 45h to 3x3 General Index Register to select Adlib/SBlaster control
;   Write 00h, adsbint_mask to 3x5 to toggle 2x6, 2xC, UADR bits 1,5 to 
;   reset chip.  Writes adsbint_mask to port to enable only what was enabled.
;   Does not do anything if Windows is running.
;
;-------------------------------------------------------------------------------
reset_SB proc near
    test cs:_shared,STAT_WINDOWS_RUNNING ; see if windows is running
    jne in_windows
    push ax
    mov dx,_reg_index     ; index register
    mov al,45h            ; irq enable/disable stuff
    out dx,al             ;
    mov dx,_reg_data_high; data register
    mov al,00h            ; toggle off 
    out dx,al             ;
    mov al,_adsbint_mask
    out dx,al             ;
    pop ax
in_windows:
    ret
reset_SB endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; decode_chan_voice - Decodes adlib register number into channel and 
;   modulator/carrier.
;
; DESCRIPTION:
;   Used only for ADLIB register writes to registers where both channel 
;   and operator are applicable. ie 20, 40, 60, 80 etc.
;   Stores results in global variables 'fm_chan', 'fm_voice'
;   fm_chan is the fm channel number for the given operator.
;   fm_voice has no relation to the fm part.  It is an array offset to a
;   voice.  0 is modulator for channel 0.  1 is carrier for channel 0. 
;   2 is modulator for channel 1 and so on...
;
; EXPECTS: BX = ADLIB register number
;          AX = ADLIB register base number for register set. ie, 20h 40h...
;
; MODIFIES:
;   global pointers
;
; SEE ALSO:
;   decode_chan
;
;-------------------------------------------------------------------------------
decode_chan_voice proc near
    ;
    ; Get channel
    ;
    sub bx,ax          ; subtract base offset from register number
    ;
    ; BX = number from 0 to 15h
    ;
    mov al,_op2chan[bx]; index op2chan to get channel number
    xor ah,ah          ; I really hate having to do this...
    mov _fm_chan,ax    ; store the channel 
    shl ax,1           ; ax is now channel modulator
    ; 
    ; Get carrier/modulator
    ;
    mov cx,bx          ; move offset into counting register
    mov ebx,1          ; start with a 1
    shl ebx,cl         ; shift left 'offset' number of times to get mask
    test _op2mod,ebx   ; test for carrier or modulator with mask
    je short @got1     ; jump on 0 - pass on 1
    jmp short @pass    ; leave to make modulator
@got1:
    add ax,1           ; add 1 to operator to make carrier
@pass:
    mov _fm_voice,ax   ; store result - this is val used in our card
    call near ptr _set_global_ptrs
    ret
decode_chan_voice endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; decode_chan - Decodes adlib register number into channel.
;
; DESCRIPTION:
;   Used for registers where only operator is applicable. ie A0, B0, C0
;   Stores results in global variables 'fm_chan', 'fm_voice'
;   fm_chan is the fm channel number for the given operator.
;   fm_voice has no relation to the fm part.  It is an array offset to a
;   voice.  0 is modulator for channel 0.  1 is carrier for channel 0. 
;   2 is modulator for channel 1 and so on... In this case, fm_voice will be
;   set to be the channel's carrier (LSB is 1)
;
; NOTE:  The code in xlat.c - process_updates() - requires (expects) that
;   the carrier will be the default coming in during a non-operator specific
;   port write.  Removing the inc bx instruction will cause unpredictable 
;   results.
;
; EXPECTS: BX = ADLIB register number
;          AX = ADLIB register base number for register set. ie, A0h B0h...
;
; MODIFIES:
;   global pointers
;
; SEE ALSO:
;   decode_chan_voice
;
;-------------------------------------------------------------------------------
decode_chan proc near
    sub bx,ax                ; subtract base offset from register number
    mov _fm_chan,bx          ; store the channel 
    shl bx,1                 ; get channel offset into fm_voice and set to mod
    inc bx                   ; now make it carrier
    mov _fm_voice,bx         ; store in fm_voice
    call near ptr _set_global_ptrs
    ret
decode_chan endp

_TEXT ends
    end
