;-------------------------------------------------------------------------------
;       NAME:  CALC.ASM $Revision: 1.9 $
;       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."
;-------------------------------------------------------------------------------
; Entry points include:
;    _asm_calc_frequency()
;    _asm_do_car_mod_freq()
;    _asm_calc_rate()
;    _asm_calc_volume()
;    _asm_calc_ksl()
;    _asm_calc_fc()
;    _asm_fm_picker()
;-------------------------------------------------------------------------------
.386

INCLUDE pick.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 _asm_calc_frequency
public _asm_do_car_mod_freq
public _asm_calc_rate
public _asm_calc_volume
public _asm_calc_ksl
public _asm_calc_fc
public _asm_fm_picker
public mpu_get_frequency
public mpu_get_bend

;---------------------------------------------------------------------
; LIST OF EXTERNS
;---------------------------------------------------------------------
extrn _iw_frequency:dword
extrn _car_frequency:dword
extrn _mod_frequency:dword
extrn _new_inst:byte
extrn _car_ptr:word
extrn _mod_ptr:word
extrn _chan_ptr:word
extrn _inst_table:word
extrn _best_inst:byte
IFDEF DEBUG
extrn _add_to_pick_list:near
ENDIF
;
; mult_table is used to lookup the right multiplication factor for the value
; written to the MULTIPLE field in the 20+ register array
; For some reason, the FM part does not allow multiples of 11,13,14
;
mult_table db 0,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15
;
; mval_table is used to lookup the 'M' value in the IW frequency calculation
;
mval_table db 255,215,176,140,107,77,48,24
;
; ksl_table is used to calculate the output attenuation caused by the KSL bits 
; in the FM part.
;
ksl_table db 168,96,72,57,48,39,33,27,24,18,15,12,9,6,3,0
;
; These are used in the picking algorithm...
;
done      db    0             ; 1 if done
best_sum  dw    0             ; best sum so far in the picker
_DATA ends

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

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; asm_calc_frequency - Calculates frequency from the ADLIB F-Num and Block
;   FM parameters.
;
; DESCRIPTION:
;
; EXPECTS:
;    AL = FreqL register on the FM part
;    CL = FreqH register on the FM part (BLOCK and F-NUMBER(H))
;
; MODIFIES:
;   EAX, CX
;
; RETURNS:
;   Returns base frequency times 1024 in iw_frequency
;
;-------------------------------------------------------------------------------
_asm_calc_frequency proc near
    and eax,000000ffh   ; Effectively clear the upper 24 bits of EAX
    mov ch,cl           ; copy for block in CH and fhigh in CL
    and ch,1ch          ; mask off all but block
    shr ch,2            ; justify block in lower bits
    and cl,03h          ; mask off all but fnumber high
    mov ah,cl           ; mov 2 bits (fhigh) to AH - get 10 bit FM frequency
    ;
    ; AX = 10 bit FM frequency
    ; CH = BLOCK
    ;
    ; The equation for what we're going to do is on the right
    ; 
    ;           50000 * Freq
    ;   freq =  ------------    or   (50000 * Freq) >> (20 - Block)
    ;           2^^(20-Block)
    ;
    imul eax,050000     ; multiply frequency by 50000 and store in EAX
    ;
    ; That took 7 bytes of opcode - it better be worth it!!!
    ;
    mov cl,10           ; 
    sub cl,ch           ; do subtract to get shift value
    shr eax,cl          ; shift frequency 20 - block times
    mov _iw_frequency,eax
    ret
_asm_calc_frequency endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; asm_do_car_mod_frequency - Calculates the frequency for the carrier and 
;    modulator based on iw_frequency and the cooresponding MULTIPLE
;
; EXPECTS:
;    AL = Carrier's Param register (20+)
;    CL = Modulator's Param register (20+)
;    iw_frequency to be a valid frequency number
;
; MODIFIES:
;   EAX, BX, ECX
;
;-------------------------------------------------------------------------------
_asm_do_car_mod_freq proc near
    push si
    and eax,0000000fh       ; get multiple and clear rest of register
    and ecx,0000000fh       ; get multiple and clear rest of register
    mov bx, offset cs:mult_table
    ;
    ; Calculate frequency for Carrier (EAX)
    ;
    mov si,ax               ; load si for indexing
    mov al,[bx+si]          ; get table value
    cmp al,0
    je short car_0          ; if ax is not 0
    imul eax,_iw_frequency  ; multiply by channel frequency
    jmp short car_done
car_0:
    mov eax,_iw_frequency
    shr eax,1               ; otherwise, multiply by .5
car_done:
    mov _car_frequency,eax
    ;
    ; Calculate frequency for Modulator (ECX)
    ;
    mov si,cx               ; load si for indexing
    mov cl,[bx+si]          ; get table value
    cmp cl,0
    je short mod_0          ; if ax is not 0
    imul ecx,_iw_frequency  ; multiply by channel frequency
    jmp short mod_done
mod_0:
    mov ecx,_iw_frequency
    shr ecx,1               ; otherwise, multiply by .5
mod_done:
    mov _mod_frequency,ecx
    pop si
    ret
_asm_do_car_mod_freq endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; asm_calc_rate - Converts the FM rate to IW an register rate
;
; DESCRIPTION:
;   Equation for Envelope rate is:
; 
;           rate = 4 * ADSR + KSR(offset)
; 
;   If the KSR bit is on -- KSR(offset) is rate >> 2
;   Otherwise KSR(offset) is just the rate
; 
; EXPECTS:
;    AL = rate from the FM chip - A,D,S,R
;    CL = 20+ register - AM-VIB-EG-KSR-MULTIPLE for that operator
;
; MODIFIES:
;   AX,DX
;
;-------------------------------------------------------------------------------
_asm_calc_rate proc near
    xor ah,ah            ; clear AH
    mov dx,ax            ; copy of rate into DL
    shl ax,2             ; AX is rate * 4
    test cl,10h          ; test for KSR bit
    je short ksr_off     ; jump if KSR bit is off
    shr dx,2             ; KSR(offset) is rate / 4 if bit is on -- see above
ksr_off:
    add ax,dx            ; return value is (int) in AX
    cmp ax,03fh          ; clamp it off at end of table
    jb short below_3f
    mov    ax,03fH
below_3f:
    ret
_asm_calc_rate endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; asm_calc_volume - Calculates the volume for the IW card based on an 
;    attenuation value in 0.75 dBs (0 - 3F).
;
; DESCRIPTION:
;                             256 + M
;   output attenuation = ------------------
;                          (512) * 2^^(15-E)
;
;   For every increase in E, the output value increases 6.02 dBs.
;   For every increase in M, the output value increases by a factor of
;   the denominator
;
;   E is integer part of (fm_attenuation / 8), the passed in value.
;   and M is from mval_table indexed by the remainder of the above division.
;
;   The FM part attenuates 0.75 dB for every increment in the LEVEL register.
;   Therefore there are 8 increments for every -6dB drop -- ie 8,16,24
;   are steps down in attenuation by .5,.25,.125.  So, there are 8 values M can
;   have for every value of E and we want to make it seem as logarithmic as
;   possible so we calculate mval_table by reversing the equation solving
;   for M.  Basically, we take the attenuation value and divide it by 8.
;   The quotient is subtracted from 15 and the remainder is the lookup into the
;   MVAL_TABLE.
;
; EXPECTS:
;   AL = attenuation value in 0.75 dB increments
;
; MODIFIES:
;   AX, BX
;
; RETURNS:
;   AX -  new 12 bit attenuation value
;
;-------------------------------------------------------------------------------
_asm_calc_volume proc near
    mov bl,al                 ; make copy
    and bx,07h                ; clear bh, bx is index now
    shr al,3                  ; divide by 8 (eight 0.75 dBs in six...)
    mov ah,15                 ; its 15 - val ...
    sub ah,al                 ; AH is 15-quotient
    ;
    ; AH = Exponent value
    ; BX = Lookup into mval_table - remainder
    ;
    add bx,offset cs:mval_table
    mov al,byte ptr [bx]      ; get table value
    ;
    ; Result is in AX - EEEEMMMMMMMM is new volume for volume register in IW
    ;
    ; Now range check the volume. Make sure its between ceiling (FBF) and 
    ; floor (700H). (FLOOR_VOL is defined in synthc.c and is >> 4)
    ;
    cmp ax,0FBFh
    jbe done_calc_vol
    mov ax,0FBFh
done_calc_vol:
    cmp ax,0060h             ; FLOOR_VOL level
    jae vol_ok
    mov ax,0061h
vol_ok:
    ret
_asm_calc_volume endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; asm_calc_ksl - Converts the Keyboard Scaling Level from FM parameters to an
;   attenuation in 0.75 dB increments
;
; DESCRIPTION:
;   Returns an attenuation value in dBs translated from the FM parameters:
;   F-NUMBER, BLOCK and KSL
;
;   The FM card attenuates the output level at higher frequencies to make 
;   it sound more like real instruments.  The attenuation level at a 
;   specific frequency is controlled by the amount of KSL given.  Valid 
;   values and effects are:
;
;        KSL Value  dB Atten
;           0          0
;           2          1.5
;           1          3
;           3          6
;
;   There is the same amount of KSL attenuation based on the upper
;   four bits of the F-NUM for every octave.
;   For each octave, the maximum attenuation is 3 times the octave number.
;   And the KSL attenuation is only part of that based on FNUM.
;   For example, there is a 6 dB maximum attenuation for the 2nd octave and 
;   as FNUM increases from 0 to 15, the attenuation increases from 0 to 6.
;   Ksl_table represents the amount of attenuation for each frequency in fnum.
;   The fnum value is used as an index into this table and the result is
;   subtracted from a total allowable attenuation for that octave. 
;   The values in the table are shifted left 4.
;   ex, 168 = 21dB     96 = 12dB    72 = 9dB etc... 
;
; EXPECTS:
;    AL = Frequency Low
;    AH = Frequency High and Block
;    CL = KSL bits
;
; MODIFIES: all registers
;
; RETURNS:
;   AL - new attenuation
;
;-------------------------------------------------------------------------------
_asm_calc_ksl proc near
    cmp cl,0           ; if the KSL value is 0...
    je short clear_atten; there is no attenuation
    and eax,0000ffffh  ; Effectively clear the upper 16 bits of EAX
    xor bx,bx          ; don't want BH contaminating things...
    mov bl,ah          ; copy block register
    and bl,1Ch         ; isolate block
    shr bl,2           ; BL is now block number
    mov dl,bl          ; copy block
    shl bl,1           ; mult block by 2
    add bl,dl          ; add block -- mult block by 3 -- BL is highest atten'n
    and ah,03h         ; isolate frequency high
    shr ax,6           ; isolate top 4 bits of frequency in AL
    xchg al,bl         ; bx is used for indexing
    shl al,3           ; make ax compatible with table elements (mult by 8)
    ;
    ; AL = starting attenuation for this octave * 8 
    ; BL = top 4 bits of FNUM - used for indexing later
    ; CL = KSL bits
    ;
    add bx,offset cs:ksl_table
    sub al,[bx]        ; AL=AL-[BX] - now holds attenuation
    ja short not_negative; if the subtract was negative - set atten to 0
clear_atten:
    xor ax,ax          ; clear attenuation and exit
    jmp short done_ksl ; outta here...
not_negative:
    cmp cl,2           ; see if KSL is 2 -- 1.5dB atten
    jne short not_2
    shr ax,1           ; use half of attenuation
not_2:
    cmp cl,3           ; see if KSL is 1 -- 3dB atten
    jne short not_3
    shl ax,1           ; use double the attenuation
not_3:                 ; If it's not 3, somebody lied and we'll use 1 (3dB)
    add ax,4           ; round up
    ;
    ; AX = attenuation in dBs times 8
    ; We must take the atten (in dBs) and multiply by 4/3 to convert to .75 dB 
    ; increments.  This will be compatible with the LEVEL parameter from the
    ; ADLIB register.  See calc_new_volume() in xlat.c
    ;
    mov bl,6
    idiv bl            ; divide AX by 6 ( (AX/8)*4/3 = AX/6 = atten (.75dB) )
    xor ah,ah
done_ksl:
    ret
_asm_calc_ksl endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; asm_calc_fc - Calculates the fc for a voice based on the normalized frequency
;   (the magic number) and the playback frequency
;
; DESCRIPTION:
;                 frequency
;          FC =   ---------
;                 magic num
;                               See sbosdata.h for details
;
; EXPECTS:
;   EAX = Frequency * 1024
;   EBX - Magic Number     - from about 0 to 6k or so
;
; MODIFIES:
;   all registers
;
; RETURNS:
;   AX - fc
;
;-------------------------------------------------------------------------------
_asm_calc_fc proc near
    xor edx,edx              ; Dividend is EDX:EAX
    cmp ebx,0
    je short div_by_zero
    idiv ebx                 ; Divide frequency by magic number
    ;
    ; EAX = quotient    - should be around 1 or so
    ; EDX = remainder   - must be less than the magic number
    ;
    ; Then to get the remainder scaled to a 10 bit value - multiply remainder
    ; by 1024 and divide by the magic number
    ;
    mov cx,ax                ; store quotient in CX - EAX is needed for divide
    xor eax,eax              ; clear EAX for divide later
    shl edx,10               ; multiply by 1024
    xchg eax,edx             ; Dividend is EDX:EAX - EDX is cleared
    cmp ebx,0
    je short div_by_zero
    idiv ebx                 ; Divide remainder by magic number
    ;
    ; AX = 10 bit scaled remainder
    ; CX = Fc integer value
    ;
    shl cx,10                ; move integer value into proper bit position
    and ax,03FFh
    or  ax,cx                ; get the final value in AX
    ret
div_by_zero:
    xor ax,ax                ; clear AX in case of div by zero
    ret
_asm_calc_fc endp

fm_sustain db ?
DR_SUST equ 1    ; decay/release of DR_SUST on FM part is 'still' sustaining

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; asm_fm_picker - Chooses the closest GM instrument for a selected range of
;   ADLIB register parameters for a voice.
;
; DESCRIPTION:
;   The closest GM instrument is found by weighting some of the selected
;   FM parameters against a set of GM FM timbres.  The result of this algorithm
;   will hopefully be the closest match possible between what is supposed to
;   be played and what will be played.  Five FM envelope parameters and one
;   global channel parameter (Feedback) are weighted against these timbres
;   and whichever timbre weights closest is the one chosen.  The weighting
;   scheme is as follows:  The final weight is the sum of a multiple of
;   the differences between the two timbres.  For example, the carrier attack
;   of the ADLIB part minus the carrier attack of one of the timbres in the
;   table (absolute value) shifted by 2 (times 4) plus the same with the carrier
;   release (except not shifted) and so on, gives the total weight for that
;   timbre.  If the FM part is setup to play a sustaining instrument, or one
;   considered to sustain (long envelope), all non sustaining instruments are
;   completely ignored.  This solves a problem with playing quick non-sustaining
;   percussion instruments in place of fast envelope sustaining melodic 
;   instruments.
;
; EXPECTS:
;   inst_table holds a list of FM timbres
;   car_ptr, mod_ptr points to carrier and modulator structures for FM channel
;
; MODIFIES:
;   all registers
;
; SEE ALSO:
;   fm_picker in pick.c
;
; RETURNS:
;   instrument choice in best_inst
;
;-------------------------------------------------------------------------------
_asm_fm_picker proc near
    push si
    ; 
    ; Setup for the looping
    ;
    mov best_sum,1000            ; start here and work down
    mov cx,0                     ; load loop start 0 to NUM_INSTRUMENTS-1
    mov done,0                   ; clear done flag
    xor ax,ax                    ; ax is running sum
    mov si, offset _inst_table   ; si is current record in table
    mov bx,_car_ptr
    cmp byte ptr [bx+4],DR_SUST  ; Does carrier decay force sustain
    jle short test_sustaining
    cmp byte ptr [bx+6],DR_SUST  ; Does carrier release force sustain
    jle short test_sustaining
    test byte ptr [bx+0],20h     ; Is carrier sustain bit on
    jne short test_sustaining
    mov fm_sustain,0             ; got here - FM note is NOT sustaining
    jmp short end_test_sustain
test_sustaining:
    mov fm_sustain,1             ; current FM note is sustaining
end_test_sustain:
    ;
    ; start of the loop
    ;
pick_loop_start:
    ;
    ; OK, I can get all the needed parameters for this instrument, so lets do it
    ;
    ; BX = pointer to car, mod, chan structures (no reload!)
    ; CX = loop counter
    ; DX = running weighted sum
    ; SI = pointer to current record in pick_table[]
    ;
    ; Sustain bit
    ;
    test byte ptr [si+3],8       ; test inst table for sustain on instrument
    je short tab_sust_off        ; if sustain off - continue with testing
    jmp short end_sustain        ; if sustain on - leave
tab_sust_off:
    ;
    ; The current instrument in the table is NOT a sustaining instrument.
    ; Now, see if FM params will make note sustain.
    ; The DR_SUST equate gives the picker the capability to choose more or less
    ; of the non sustaining instruments.  As DR_SUST grows, more of the FM
    ; parameters that come through here are considered sustaining instruments
    ; and the picker will not choose a non sustaining instrument. (whew!)
    ; As the equate gets smaller, more non sustaining instruments will get
    ; picked.  Basically lower this if you want more percussions.
    ;
    cmp fm_sustain,1
    je short sustaining
    ;
    ; FM parameters say this note is NOT sustaining
    ; This note is not sustaining - doesn't matter what instrument we pick
    ;
    jmp short end_sustain
sustaining:
    ;
    ; FM parameters says this note IS sustaining
    ; This note is sustaining but instrument doesn't - this is bad
    ;
    jmp dont_add_at_all       ; skip this instrument and don't add to debug tbl
end_sustain:
    ;
    ; Carrier attack
    ;
    mov bx,word ptr _car_ptr  ; load offset carrier pointer
    mov al,byte ptr [si+1]
    shr al,4                  ; attack is top nibble
    sub al,byte ptr [bx+3]    ; subtract carrier attack
    jg short ca_above
    neg al                    ; absolute value of al
ca_above:
    shl al,WEIGHT_CA          ; weight carrier attack
    cbw
    mov dx,ax                 ; save into intermediate value - ah should be 0
IFNDEF DEBUG
    cmp dx,best_sum           ; See if we already lost
    ja not_good_enough
ENDIF
    ;
    ; Carrier release
    ;
    mov al,byte ptr [si+1]
    and al,0fh                ; release is bottom nibble
    sub al,byte ptr [bx+6]    ; subtract carrier release
    jg short cr_above
    neg al                    ; absolute value of al
cr_above:
    shl al,WEIGHT_CR          ; weight carrier release
    cbw
    add dx,ax                 ; save into intermediate value
IFNDEF DEBUG
    cmp dx,best_sum           ; See if we already lost
    ja not_good_enough
ENDIF
    ;
    ; Modulator attack
    ;
    mov bx,word ptr _mod_ptr  ; load offset modulator pointer
    mov al,byte ptr [si+2]
    shr al,4                  ; attack is top nibble
    sub al,byte ptr [bx+3]    ; subtract modulator attack
    jg short ma_above
    neg al                    ; absolute value of al
ma_above:
    cbw
    add dx,ax                 ; save into intermediate value
IFNDEF DEBUG
    cmp dx,best_sum           ; See if we already lost
    ja short not_good_enough
ENDIF
    ;
    ; Modulator release
    ;
    mov al,byte ptr [si+2]
    and al,0fh                ; release is bottom nibble
    sub al,byte ptr [bx+6]    ; subtract modulator release
    jg short mr_above
    neg al                    ; absolute value of ax
mr_above:
    cbw
    add dx,ax                 ; save into intermediate value
IFNDEF DEBUG
    cmp dx,best_sum           ; See if we already lost
    ja short not_good_enough
ENDIF
    ;
    ; Modulator sustain level
    ;
    mov al,byte ptr [si+3]
    shr al,4                  ; modulator sustain is top nibble
    sub al,byte ptr [bx+5]
    jg short ms_above
    neg al                    ; absolute value of ax
ms_above:
    shl al,WEIGHT_MS          ; weight modulator sustain by something...?
    cbw
    add dx,ax                 ; save into intermediate value
IFNDEF DEBUG
    cmp dx,best_sum           ; See if we already lost
    ja short not_good_enough
ENDIF
    ;
    ; Feedback
    ;
    mov bx,word ptr _chan_ptr ; load offset channel pointer
    mov al,byte ptr [si+3]
    and al,7                  ; feedback is bottom 3 bits
    mov bl,byte ptr [bx+3]
    shr bl,1
    and bl,07h
    sub al,bl                 ; subtract 
    jg short fb_above
    neg al                    ; absolute value of ax
fb_above:
    shl al,WEIGHT_FB          ; weight feedback by 2
    cbw
    add dx,ax                 ; save into intermediate value
IFNDEF DEBUG
    cmp dx,best_sum           ; See if we already lost
    ja short not_good_enough
ENDIF
    ;
    ; Done with weighting - now see how it compares to the others...
    ;
    mov al,byte ptr [si]      ; Load the instrument number from the table
IFNDEF DEBUG
    cmp dx,MIN_WEIGHT         ; Is the weight for this instrument close enough?
    ja short bad_weight       ; If dx is above the min, goto next test
    mov _best_inst,al         ; If its less, save the instrument number
    mov done,1                ; and get out of here
    jmp not_good_enough       
ENDIF
bad_weight:
    cmp dx,best_sum           ; Otherwise, see if it beats the last one
    jae short not_good_enough
    mov best_sum,dx           ; if it is, save the sum
    mov _best_inst,al         ; and the instrument number and continue on
not_good_enough:
IFDEF DEBUG
    push cx
    push dx                   ; The current instrument's sum
    push ax                   ; The current instrument's number
    call near ptr _add_to_pick_list
    add sp,4
    pop cx
    xor ax,ax   ; ah should be 0
ENDIF
dont_add_at_all:
    ;
    ; Loop back up if I need to
    ;
    inc cx                    ; Nope, inc counter
    cmp cx,NUM_INSTRUMENTS    ; Have we reached the limit?
    jge short pick_done       ; leave
    cmp done,1                ; Or are we forcing completion?
    je short pick_done        ; leave
    add si,NUM_CHARS          ; update the index into pick_table
    jmp pick_loop_start       ; and loop back up
pick_done:
    pop si
    ret
_asm_fm_picker endp

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; mpu_get_frequency - Calculates the frequency in Hertz of a GM note
;
; DESCRIPTION:
;
; EXPECTS:
;
; MODIFIES:
;
; SEE ALSO:
;
; RETURNS:
;
;-------------------------------------------------------------------------------
mpu_get_frequency proc near
    push bp
    mov bp,sp
    mov ax,[bp+4]
    xor ah,ah
    and al,07fh         ; make sure it's a valid number
    mov bl,12
    idiv bl             ; AX/BL -> AH-remainder -- AL-quotient
    mov cl,10 
    sub cl,al           ; cl is 10-octave
    mov bx,offset cs:freq_table
    shr ax,8            ; put ah in al and zero extend
    shl ax,2            ; for dword
    add bx,ax           ; get table index
    mov eax,[bx]
    shr eax,cl
    mov edx,eax
    shr edx,16
    pop bp
    ret 2
mpu_get_frequency endp

; This is octave 10 - extrapolated out two more notes
freq_table dd   8572947,   9082720,   9622807,  10195009,  10801236,  11443511
dd              12123977,  12844906, 13608704, 14417920, 15275254, 16183568

;-------------------------------------------------------------------------------
;
; FUNCTION_DEFINITION:
; mpu_get_bend -
;
; DESCRIPTION:
;
; EXPECTS:
;
; MODIFIES:
;
; SEE ALSO:
;
; RETURNS:
;
;-------------------------------------------------------------------------------
mpu_get_bend proc near
    push bp
    mov bp,sp
    mov eax,[bp+4]       ; AX = pitch bend value
    push si
    push di
    mov bx,ax
    shr eax,13           ; AX = semitones = pitch bend value >> 13
    push ax            ;; SAVE IT for later
    and bx,1fffh        ; BX = mantissa  = AX % 8192 (0x2000)
    xor dx,dx          ; dx:ax for divide
    mov cx,12           ; 12 semitones per octave
    idiv cx             ; dx:ax/12 -> DX-remainder -- AX-quotient
    mov si,dx
    shl si,1
    mov di,word ptr [pb_log_table+si]
    mov cl,al           ; get quotient and use as power
    shl di,cl
    pop ax
    push di             ; push first point
    inc ax
    xor dx,dx          ; dx:ax for divide
    mov cx,12
    idiv cx             ; do divide for second point
    mov si,dx
    shl si,1
    mov di,word ptr [pb_log_table+si]
    mov cl,al
    shl di,cl           ; second point
    pop si
    ; BX = mantissa
    ; SI = point1
    ; DI = point2
    xor edx,edx
    mov dx,di           ; point2
    sub dx,si           ; DX = point2 - point1
    xor eax,eax
    mov ax,bx
    imul eax,edx        ; ((((f2 -f1) * mantissa) >> 13) + f1)
    shr eax,13        
    add ax,si
    cmp ax,0
    jne ax_not_0
    mov ax,1
ax_not_0:
    pop di
    pop si
    pop bp
    ret 2
mpu_get_bend endp

pb_log_table dw 1024,1085,1149,1218,1290,1367,1448,1534,1625,1722,1825,1933

_TEXT ends
end
