Metropoli BBS
VIEWER: gus.asm MODE: TEXT (CP437)
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
;
; Sound support - GUS & SB
;
; Labels begining with:
;  _sfx - generic sound commands, both cards
;  _gs  - GUS labels
;  _sb  - Sound Blaster labels
;
; Notes and Examples:
;
;  call _sfx_find              ; for both soundcards
;  jc no sound card installed
;
;  mov edx,_lomembase          ; DMA buffer        * ALL this data is ignored *
;  mov ebx, 1024               ; DMA buffer size   *  if the user has a GUS   *
;  mov ecx, 22000              ; sample rate
;  mov eax,_lomembase
;  add eax, ebx                ; samples are right after DMA buffer
;  call _sfx_init              ; call this for both soundcards (SB and GUS)
;  mul ebx,200000              ; 200000 bytes of sample memory required (0 if GUS)
;  add _lomembase,eax          ; bump up low memory because of DMA (0 if GUS)
;  add _lomembase,ebx          ; bump up low memory because of samples required (0 if GUS)
;
;  call _sfx_uninit            ; done, clear soundcard
;
; Notes: Original GUS code (now modified) from GS.ASM by Thomas Pytel, aka TRAN
;        Sound Blaster code (Gravis Emulation) by John McCarthy, aka FLYNN.
;
; Send me a postcard!
;
;        John McCarthy
;        1316 Redwood Lane
;        Pickering, Ontario.
;        Canada, Earth, Milky Way (for those out-of-towners)
;        L1X 1C5
;
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

         .386p
code32   segment para public use32
         assume cs:code32, ds:code32

         include pmode.ext
         include irq.ext
         include file.ext

         public _sfx_find                   ; find if sound device available
         public _sfx_init                   ; intialize sound device
         public _sfx_uninit                 ; unintialize sound device
         public _sfx_putram                 ; put to ram
         public _sfx_getram                 ; get from ram
         public _sfx_shutup                 ; turn single voice off
         public _sfx_relvolume              ; relative volume control
         public _sfx_setvolume              ; set volume of channel
         public _sfx_getfreq

         public _freqtbl                    ; precalculated frequencies for c0 to b4

         public _sfxnumvoices               ; number of voices to mix
         public _sfxmem                     ; next available location in sample memory
         public _sfxram                     ; amount of RAM on card
         public _sfxsign                    ; sign for XOR'ing of samples (only during
                                            ; initial transfer to card)
         public _sfxtype                    ; card installed, 0 = none, 1 = GUS, 2 = SB

         public _vccmnd, _vccntrl, _vcsbeg, _vclbeg, _vclend, _vcfreq, _vcpan, _vcvol
         public _mix_volume

         align 4

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Generic voice handling stuff
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_sfx_init   dd offset _return_null          ; jump vectors to appropriate code
_sfx_uninit dd offset _return_null
_sfx_putram dd offset _return_null
_sfx_getram dd offset _return_null
_sfx_shutup dd offset _return_null
_sfx_getfreq dd offset _return_null

_sfxsign db 0                               ; signed data not, 0 = SB, 80h = GUS
_sfxmem  dd 0                               ; next free location in GUS or SB virtual memory
_sfxram  dd 0                               ; amount of available GUS or SB virtual memory
_sfxnumvoices db 16                         ; number of voices (up to 32)
_sfxmiddlec dw 8664                         ; Hz assigned to middle C
_sfxtype db 0                               ; card installed, 0 = none, 1 = GUS, 2 = SB
                                            ; bit 1, 1 = virtual ram or 0 = GUS ram/no ram

_vccmnd  db 32 dup(?)                       ; virtual channel command bits
                                            ;  bit 0: volume change
                                            ;  bit 1: balance change
                                            ;  bit 2: frequency change
                                            ;  bit 3: sample note-on
                                            ;  bit 4: sample continues into next chunk (SB only)
_vccntrl db 32 dup(?)                       ; virtual channel voice control
                                            ;  bit 3: enable loop
                                            ;  bit 4: bi-directional loop
                                            ;  bit 6: 0=forward, 1=reverse
_vcsbeg  dd 32 dup(?)                       ; virtual channel sample begin
_vclbeg  dd 32 dup(?)                       ; virtual channel loop begin
_vclend  dd 32 dup(?)                       ; virtual channel loop end
_vcfreq  dw 32 dup(?)                       ; virtual channel frequency value
_vcpan   db 32 dup(?)                       ; virtual channel pan position
_vcvol   db 32 dup(?)                       ; virtual channel volume (high byte)

_mix_volume db 32 dup(15)                   ; mixing volume (each channel)

aclbeg   dd 32 dup(?)                       ; actual channel loop begin
aclend   dd 32 dup(?)                       ; actual channel loop end
acfreq   dw 32 dup(?)                       ; actual channel frequency value
acpan    db 32 dup(?)                       ; actual channel pan position
acvol    db 32 dup(?)                       ; actual channel volume (high byte)
acpos    dd 32 dup(?)                       ; actual channel position           (SB only)
acstep   db 32 dup(?)                       ; actual channel last step position (SB only)
accntrl  db 32 dup(?)                       ; actual channel voice control      (SB only)
accmnd   db 32 dup(?)                       ; actual channel command            (SB only)

_freqtbl dw 60 dup (0)

rawfreq  dw 2166,2294,2431,2573,2728,2891,3063,3245,3438,3642,3859,4088
         dw 4332,4589,4862,5147,5457,5782,6126,6490,6876,7285,7718,8177
         dw 8664,9179,9725,10294,10915,11565,12252,12981,13752,14571,15437,16355
         dw 17328,18358,19450,20589,21831,23130,24505,25962,27505,29142,30874,32710
         dw 34656,36716,38900,41179,43663,46260,49011,51925,55010,58284,61749,65421

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Null soundcard (default)
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_return_null:
         xor eax,eax
         xor ebx,ebx
         xor ecx,ecx
         xor edx,edx
         xor esi,esi
         xor edi,edi
         xor ebp,ebp
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Macros
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

         vol = 1                            ; alter volume
         pan = 2                            ; alter pan position
         freq = 4                           ; alter frequency
         play = 8                           ; start/re-start sample
         cont = 16                          ; continue with SB sample (for next chunk, SB only)
         shutup = 32                        ; turn channel completly off

         loop_on = 8                        ; looping enable
         loop_bi = 16                       ; bi-directional loop
         loop_rev = 64                      ; loop direction, 1 = backwards

gusoutb  macro index,value
         mov al,&index
         out dx,al
         add dl,2
         mov al,&value
         out dx,al
         sub dl,2
endm

gusoutw  macro index,value
         mov al,&index
         out dx,al
         inc edx
         mov ax,&value
         out dx,ax
         dec edx
endm

gusinb   macro reg,index
         mov al,&index
         out dx,al
         add dl,2
         in al,dx
         sub dl,2
         ifnb <reg>
         mov reg,al
         endif
endm

gusinw   macro reg,index
         mov al,&index
         out dx,al
         inc edx
         in ax,dx
         dec edx
         ifnb <reg>
         mov reg,ax
         endif
endm

sbout    macro data
         local wait
wait:    in al,dx
         or al,al
         js wait
         mov al,data
         out dx,al
         endm

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Perform Initial test for card:  Set jump vectors for card found
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_sfx_find:
         mov _sfxtype,0

         call _gus_find
         jc fd_looksb

         mov _sfx_init ,offset _gus_init
         mov _sfx_uninit,offset _gus_uninit
         mov _sfx_putram,offset _gus_putram
         mov _sfx_getram,offset _gus_getram
         mov _sfx_shutup,offset _gus_shutup
         mov _sfx_getfreq,offset _gus_getfreq
         mov _sfxtype,1
         mov _sfxsign,0
         ret

fd_looksb:
         call _sb_find
         jc _ret
         mov _sfx_init ,offset _sb_init
         mov _sfx_uninit,offset _sb_uninit
         mov _sfx_putram,offset _sb_putram
         mov _sfx_getram,offset _sb_getram
         mov _sfx_shutup,offset _sb_shutup
         mov _sfx_getfreq,offset _sb_getfreq
         mov _sfxtype,2
         mov _sfxsign,0
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Set mixing volume of channel EBX
; In:
;  EBX - voice channel #
;  AL  - volume level
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_sfx_setvolume:
         cmp al,0
         jge sv_1
         mov al,0
sv_1:
         cmp al,15
         jle sv_2
         mov al,15
sv_2:
         mov _mix_volume[ebx],al
         or _vccmnd[ebx],vol
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Change volume of channels (relative)
; In:
;   BL - channel to start at
;   CL - channel to end at
;   DL - 1 = inc, -1 = dec
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_sfx_relvolume:
         sub cl,bl
         inc cl
         movzx ebx,bl
         movzx ecx,cl
av_1:
         mov al,_mix_volume[ebx]
         add al,dl
         call _sfx_setvolume
         inc ebx
         loop av_1

         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; GUS lowlevel interface. Part II - modified from TRAN's original gs.asm
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

         public _gus_handler
         public _gus_shutup
         public _gus_init
         public _gus_uninit
         public _gus_putram
         public _gus_getram
         public _gus_find
         public _gus_getfreq

         public _gusport
         public _gusirq

ormgusirqvect dd ?                          ; old real mode GF1 IRQ vector
rmgusirqbuf db 21 dup(?)                    ; buffer for rm GF1 IRQ callback code

gusport102 dw ?                             ; GUS port + 102h
gusport103 dw ?                             ; GUS port + 103h
gusport104 dw ?                             ; GUS port + 104h
gusport107 dw ?                             ; GUS port + 107h

_gusport dw 220h
_gusirq  db 11

whichguscontrol dd -1                       ; irq timing control number

port103val   db 43h                         ; value to set on exit from IRQ
irqm0tbl     dw 0c089h,0a0e6h
gusirqvaltbl db 0,0,41h,43h,0,42h,0,44h,0,0,0,45h,46h,0,0,47h

voltbl   db 004h,0a0h,0b0h,0c0h,0c8h,0d0h,0d8h,0e0h
         db 0e4h,0e8h,0ech,0f0h,0f2h,0f4h,0f6h,0f8h

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Gus IRQ - useless, never called
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

gus_irq:
         push eax
         mov al,20h
         out 20h,al
irqm0    dw ?                               ; out 0a0h,al | mov eax,eax
         sti
         cld
         push ebx ecx edx esi edi ebp ds
         mov ds, [cs:_seldata]



         pop ds ebp edi esi edx ecx ebx eax
         iretd

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Gus voice handler
; Update GUS from virtual values/commands
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_gus_handler:
         mov dx,gusport103
         movzx ebp,_sfxnumvoices
         dec ebp
irql0:
         xor cl,cl
         xchg cl,_vccmnd[ebp]
         or cl,cl
         jz irql0c

         test cl,shutup
         jz short irql0f9
         mov edi,ebp
         call _gus_shutup
         jmp irql0c
irql0f9:
         test cl,play
         jz short irql0f0
         or cl,vol+pan+freq
irql0f0:

         mov eax,ebp
         dec edx
         out dx,al
         inc edx

         cmp _mix_volume[ebp],0
         jne irql0g1
         gusoutb 0,1
         jmp irql0c

irql0g1:
         test cl,freq
         jz short irql0f1
         mov bx,_vcfreq[ebp*2]
         cmp bx,acfreq[ebp*2]
         je short irql0f1
         mov acfreq[ebp*2],bx
         gusoutw 1,bx
irql0f1:

         test cl,pan
         jz short irql0f2
         mov bl,_vcpan[ebp]
         cmp bl,acpan[ebp]
         je short irql0f2
         mov acpan[ebp],bl
         gusoutb 0ch,bl
irql0f2:

         test cl,play
         jz irql0f3
         mov esi,_vclbeg[ebp*4]
         cmp esi,aclbeg[ebp*4]
         je short irql0f2f0
         mov aclbeg[ebp*4],esi
         shrd bx,si,7
         shr esi,7
         gusoutw 2,si
         gusoutb 3,bh
irql0f2f0:
         mov esi,_vclend[ebp*4]
         cmp esi,aclend[ebp*4]
         je short irql0f2f1
         mov aclend[ebp*4],esi
         shrd bx,si,7
         shr esi,7
         gusoutw 4,si
         gusoutb 5,bh
irql0f2f1:
         mov esi,_vcsbeg[ebp*4]
         shrd bx,si,7
         shr esi,7
         gusoutw 0ah,si
         gusoutb 0bh,bh
         gusoutb 0,_vccntrl[ebp]
irql0f3:

         test cl,vol
         jz short irql0f4
         movzx ebx,_vcvol[ebp]
         mov bl,voltbl[ebx]
         cmp bl,4
         jbe irql0f3f4
         mov bh,_mix_volume[ebp]
         shl bh,3
         sub bh,_mix_volume[ebp]            ; *7
         add bl,bh
         sub bl,105                         ; 7*15
irql0f3f4:
         mov bh,acvol[ebp]
         mov ah,bh
         cmp bh,bl
         je short irql0f4
         mov ch,40h
         ja short irql0f3f0
         xchg bl,bh
         xor ch,ch
irql0f3f0:
         gusoutb 7,bl
         gusoutb 8,bh
         gusoutb 9,ah
         gusoutb 0dh,ch
irql0f4:

         mov dx,300h
         db 7 dup(0ech)                     ; in al,dx
         mov dx,gusport103

         test cl,vol
         jz short irql0f5
         movzx ebx,_vcvol[ebp]
         mov al,voltbl[ebx]
         cmp al,4
         jbe irql0f5f4
         mov bh,_mix_volume[ebp]
         shl bh,3
         sub bh,_mix_volume[ebp]            ; *7
         add al,bh
         sub al,105                         ; 7*15
irql0f5f4:
         cmp al,ah
         je short irql0f5
         mov acvol[ebp],al
         gusoutb 9,ah
         gusoutb 0dh,ch
irql0f5:

         test cl,play
         jz short irql0c
         mov esi,_vcsbeg[ebp*4]
         shrd bx,si,7
         shr esi,7
         gusoutw 0ah,si
         gusoutb 0bh,bh
         gusoutb 0,_vccntrl[ebp]

irql0c:
         sub ebp,1
         jnc irql0

         outb port103val
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; A small delay, In: ECX - number of times to do delay
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

gusdelay:
         push ax dx
         mov dx,300h
gusdelayl0:
         in al,dx
         in al,dx
         in al,dx
         in al,dx
         in al,dx
         in al,dx
         in al,dx
         in al,dx
         in al,dx
         loop gusdelayl0
         pop dx ax
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Shove some crap into GUS ram
; In:
;   EBX - addx in GUS ram to shove into
;   ECX - amount of crap to shove
;   EDX -> actual crap to shove
; Notes:
;  All incoming data will be XOR'ed with _sfxsign  !!!
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
_gus_putram:
         push eax ebx ecx edx esi
         push ecx
         mov al,_sfxsign
         xor al,80h
gsputramlx:
         xor byte ptr [edx],al
         inc edx
         loop gsputramlx
         pop ecx
         sub edx,ecx
         mov esi,edx
         mov dx,gusport104
gsputraml0:
         dec edx
         mov al,44h
         mov port103val,al
         out dx,al
         add dl,2
         shld eax,ebx,16
         out dx,al
         sub dl,2
         mov al,43h
         mov port103val,al
         out dx,al
         inc edx
gsputraml1:
         mov ax,bx
         out dx,ax
         add dl,3
         outsb
         sub dl,3
         inc bx
         loopnz gsputraml1
         jecxz short gsputramd
         add ebx,10000h
         jmp gsputraml0
gsputramd:
         pop esi edx ecx ebx eax
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Rip some crap outta GUS ram
; In:
;   EBX - addx in GUS ram to rip from
;   ECX - amount of crap to rip
;   EDX -> buffer for ripped crap
; Notes:
;  All outgoing data will be XOR'ed with _sfxsign  !!!
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
_gus_getram:
         push eax ebx ecx dx edi
         push ecx
         mov al,_sfxsign
         xor al,80h
gsgetramlx:
         xor byte ptr [edx],al
         inc edx
         loop gsgetramlx
         pop ecx
         sub edx,ecx
         mov edi,edx
         mov dx,gusport104
gsgetraml0:
         dec edx
         mov al,44h
         mov port103val,al
         out dx,al
         add dl,2
         shld eax,ebx,16
         out dx,al
         sub dl,2
         mov al,43h
         mov port103val,al
         out dx,al
         inc edx
gsgetraml1:
         mov ax,bx
         out dx,ax
         add dl,3
         insb
         sub dl,3
         inc bx
         loopnz gsgetraml1
         jecxz short gsgetramd
         add ebx,10000h
         jmp gsgetraml0
gsgetramd:
         pop edi dx ecx ebx eax
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Initialize GUS
; In: none
; Out:
;   EAX = 0
;   EBX = 0 (can be used for determining if memory requuired on cpu, SB returns 1)
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
_gus_init:
         pushad
         mov ax,900h
         int 31h
         push ax

         mov dx,_gusport                    ; set up all port vars
         outb 0bh
         add dx,102h
         mov gusport102,dx
         inc edx
         mov gusport103,dx
         inc edx
         mov gusport104,dx
         add dl,3
         mov gusport107,dx

         sub dl,4                           ; initialize GUS
         outb 4ch
         add dl,2
         outb 0
         mov ecx,10h
         call gusdelay
         outb 1
         sub dl,2
         mov ecx,10h
         call gusdelay
         gusoutb 41h,0
         gusoutb 45h,0
         gusoutb 49h,0
         outb 0eh
         add dl,2
         mov al,_sfxnumvoices
         dec al
         or al,0c0h
         outb al
         sub dl,3

         mov bl,1fh                         ; set up all voices
gsinitl0:
         mov al,bl
         and al,7fh
         out dx,al
         inc edx
         gusoutw 9,0
         gusoutb 0,0
         gusoutb 0dh,3
         gusoutb 6,16
         outb 0ch
         add dl,2
         outb 7
         sub dl,3
         mov ecx,1
         call gusdelay
         xor bl,80h
         js gsinitl0
         sub bl,1
         jnc gsinitl0

         sub dx,0f3h                        ; damn, another undocumented port
         outb 5
         sub dl,0fh
         outb 8
         add dl,0bh
         xor al,al
         out dx,al
         add dl,4
         out dx,al
         sub dl,0fh

         movzx ebx,_gusirq                  ; set IRQ channels
         outb 4bh
         add dl,0bh
         mov al,gusirqvaltbl[ebx]
         out dx,al
         sub dl,0bh
         outb 9
         add dx,103h

         outb 43h                           ; how much RAM on the card
         inc edx
         outw 0ffffh
         dec edx
         outb 44h
         add dl,2
         mov cl,3
gsinitl1:
         outb cl
         add dl,2
         in al,dx
         inc eax
         mov ah,al
         out dx,al
         in al,dx
         sub dl,2
         cmp al,ah
         jne short gsinitl1d
         inc _sfxram
         add cl,4
         test cl,10h
         jz gsinitl1
gsinitl1d:
         sub dl,2
         shl _sfxram,18

         gusoutb 4ch,7                      ; enable GUS normal operation

         cmp bl,2                           ; set and enable GF1 IRQ (BL=IRQ num)
         jne short $+4
         mov bl,9
         cmp bl,7
         seta al
         movzx eax,al
         mov ax,irqm0tbl[eax*2]
         mov irqm0,ax
         mov edx,offset gus_irq
         call _setirqvect
         xor al,al
         call _setirqmask
         mov edi,offset rmgusirqbuf
         call _rmpmirqset
         mov ormgusirqvect,eax

         mov whichguscontrol,-1
         call _irq_findcontrol
         jc gusnogus
         mov _irqcontrol[ecx*4],offset _gus_handler
         mov whichguscontrol,ecx
gusnogus:
         mov ecx,60                         ; calculate frequency tables
         mov bl,_sfxnumvoices
         dec bl
         and ebx,1fh
         add bl,1
         mov si,16
gusfreqcalc:
         mov ax,rawfreq[ecx*2-2]
         call _gus_getfreq
         mov _freqtbl[ecx*2-2],ax
         loop gusfreqcalc

         pop ax
         int 31h
         popad
         xor ebx,ebx                        ; memory flag, 0 = all samples on GUS
         xor eax,eax                        ; DMA memory flag. 0 = sound blaster DMA not used
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Uninitialize GUS
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_gus_uninit:
         push ax bx edx
         mov ax,900h
         int 31h
         push ax

         mov bl,_gusirq                     ; Kill GUS IRQ handler
         cmp bl,2
         jne short $+4
         mov bl,9
         mov eax,ormgusirqvect
         call _rmpmirqfree
         mov al,1
         call _setirqmask

         mov dx,gusport103                  ; Shut down GUS
         outb 4ch
         add dl,2
         outb 0
         sub dx,105h
         outb 0bh

         mov ecx,whichguscontrol
         or ecx,ecx
         jl gusnovoice
         mov _irqcontrol[ecx*4],offset _ret
gusnovoice:

         pop ax
         int 31h
         pop edx bx ax
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Find a GUS (environment variable)
; Out:
;   EAX,ECX,ESI,EDI - ?
;   CF=0 - found GUS
;   CF=1 - no GUS found
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
pspultrastr db 'ULTRASND'

_gus_find:
         pushad
         mov esi,_pspa
         movzx esi,word ptr gs:[esi+2ch]
         shl esi,4
gsfindl3:
         mov edi,offset pspultrastr
         mov ecx,8
         repe cmps byte ptr gs:[esi],byte ptr es:[edi]
         jne short gsfindf0
         mov edi,10h
         call gsfindr0
         mov _gusport,cx
         call gsfindr0
         call gsfindr0
         mov edi,10
         call gsfindr0
         mov _gusirq,cl
         popad
         clc
         ret
gsfindf0:
         lea esi,[esi+ecx-8]
gsfindl0:
         inc esi
         cmp byte ptr gs:[esi-1],0
         jne gsfindl0
         cmp byte ptr gs:[esi],0
         jne gsfindl3
         popad
         stc
         ret

gsfindr0:
         movzx eax,byte ptr gs:[esi]
         inc esi
         sub al,'0'
         jc gsfindr0
         cmp al,9
         ja gsfindr0
         mov ecx,eax
gsfindr0l0:
         mov al,gs:[esi]
         inc esi
         sub al,'0'
         jc gsfindr0d
         cmp al,9
         ja gsfindr0d
         imul ecx,edi
         add ecx,eax
         jmp gsfindr0l0
gsfindr0d:
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Shut voice EDI up on GUS
; In:
;   EDI - voice to kill
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
_gus_shutup:
         push edx eax ebx
         mov _vccmnd[edi],0
         mov dx,gusport103
         mov eax,edi
         dec edx                            ; 3X2
         out dx,al
         inc edx                            ; 3X3
         gusinb bl,89h
         gusoutb 6,64+48
         gusoutb 7,4
         gusoutb 8,bl
         gusoutb 0dh,40h
         pop ebx eax edx
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Convert actual frequency to GUS frequency number
; In:
;   EAX - frequency
; Out:
;   AX - GUS frequency number
;   EAX high word - ?
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
_gus_getfreq:
         push edx ebx ebp
         xor ebp,ebp
         mov bp,_sfxmiddlec
         mul ebp
         mov ebp,8664
         div ebp
         xor edx,edx
         shl eax,10
         xor ebx,ebx
         mov bl,_sfxnumvoices
         cmp bl,14
         jae short $+4
         mov bl,14
         mov bx,freqact[ebx*2-14*2]
         div ebx
         pop ebp ebx edx
         ret

freqact  dw 44100,41160,38587,36317,34300,32494,30870,29400,28063
         dw 26843,25725,24696,23746,22866,22050,21289,20580,19916,19293

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; SB lowlevel interface. - Everyone knows that the SB is OUTDATED and that
; anyone whose anybody has a GUS!  These routines are for those losers...
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

         public _sb_find
         public _sb_init
         public _sb_uninit
         public _sb_putram
         public _sb_getram
         public _sb_get_dmacount
         public _sb_getfreq

         public _sbport
         public _sbirq
         public _sbdmabuffer
         public _sbdmasize
         public _sbmixspeed
         public _sbvoltables
         public _sbsamples
         public _sbvolume

_sbport  dw 0
_sbirq   db 0

_sbdmabuffer dd 0                           ; DMA buffer
_sbdmasize   dd 0                           ; DMA buffer size
_sbmixspeed  dw 0                           ; mixing speed, (11000,22000...)
_sbvoltables dd 0                           ; pointer to volume tables
_sbsamples   dd 0                           ; sample tables
_sbvolume    db 50                          ; overall volume diminishing for samples (256=full) init only
sbebp    dd 0                               ; temp ebp 2
dmaxor   dd 0                               ; xor value to flip between dma half buffers
dmachunk dd 0                               ; current dma chunk location (front/back)
dmahalf  dd 0                               ; _sbdmasize /2
samend   dd 0                               ; current sample end location
saveebp  dd 0                               ; temp save of ebp

ormsbirqvect dd ?                           ; old real mode SB IRQ vector
rmsbirqbuf db 21 dup(?)                     ; buffer for rm SB IRQ callback code

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Find a SB (environment variable)
; Out:
;   EAX,ECX,ESI,EDI - ?
;   CF=0 - found GUS
;   CF=1 - no SB found
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

pspsbstr db 'BLASTER'

_sb_find:
         pushad
         mov esi,_pspa
         movzx esi,word ptr gs:[esi+2ch]
         shl esi,4
sbfindl3:
         mov edi,offset pspsbstr
         mov ecx,7
         repe cmps byte ptr gs:[esi],byte ptr es:[edi]
         jne short sbfindf0

         mov ebx,esi
         mov ecx,10h
         mov al,"A"
         mov ah,"a"
         dec esi
sbf_loop1:
         inc esi
         cmp byte ptr gs:[esi],ah
         je sbf_it1
         cmp byte ptr gs:[esi],al
         loopne sbf_loop1
sbf_it1:
         mov edi,10h
         call sbfindr0
         mov _sbport,cx

         mov ecx,10h
         mov al,"I"
         mov ah,"i"
         dec ebx
sbf_loop2:
         inc ebx
         cmp byte ptr gs:[ebx],ah
         je sbf_it2
         cmp byte ptr gs:[ebx],al
         loopne sbf_loop2
sbf_it2:
         mov esi,ebx

         mov edi,10
         call sbfindr0
         mov _sbirq,cl
         popad
         clc
         ret
sbfindf0:
         lea esi,[esi+ecx-7]
sbfindl0:
         inc esi
         cmp byte ptr gs:[esi-1],0
         jne sbfindl0
         cmp byte ptr gs:[esi],0
         jne sbfindl3
         popad
         stc
         ret

sbfindr0:
         movzx eax,byte ptr gs:[esi]
         inc esi
         sub al,'0'
         jc sbfindr0
         cmp al,9
         ja sbfindr0
         mov ecx,eax
sbfindr0l0:
         mov al,gs:[esi]
         inc esi
         sub al,'0'
         jc sbfindr0d
         cmp al,9
         ja sbfindr0d
         imul ecx,edi
         add ecx,eax
         jmp sbfindr0l0
sbfindr0d:
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Shut voice EDI up on SB
; In:
;   EDI - voice to kill
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
_sb_shutup:
         mov _vccmnd[edi],0
         mov accmnd[edi],0
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Sound Blaster IRQ handler
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

sb_irq:
         push eax
         mov al,20h                         ; re-enable irq
         out 20h,al
irqm00   dw ?                               ; out 0a0h,al | mov eax,eax
         sti
         cld
         push ebx ecx edx esi edi ebp ds
         mov ds, [cs:_seldata]

         mov dx,_sbport                     ; acknowlege sound blaster
         add dx,0eh
         in al,dx

         mov eax,dmaxor                     ; we are now transferring other section...
         xor dmachunk,eax                   ; flip to other dma chunk

         call wipe_dmachunk
         call _sb_handler

         pop ds ebp edi esi edx ecx ebx eax
         iretd

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Wipe current dma chunk
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

wipe_dmachunk:
         push edi ecx eax
         mov ecx,dmahalf
         shr ecx,2
         mov edi,dmachunk
         mov eax,80808080h
         rep stosd
         pop eax ecx edi
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Get current DMA location
; Out:
;  ECX = > DMA load location
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_sb_get_dmacount:
         push eax
         xor ecx,ecx
         in al,03h                          ; get from dma controller
         mov cl,al
         in al,03h
         mov ch,al
         neg ecx
         add ecx,_sbdmasize
         add ecx,_sbdmabuffer
         dec ecx
         pop eax
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Initialize SB
;  In:
;   EDX = > free memory for DMA buffer (must be in low memory!)
;   EAX = > free memory for volume tables, samples (MUST BE HIGHER THAN EDX!!)
;   EBX = > DMA buffer size (dword aligned)
;    CX = mixing speed (11000, 22000...)
; Out:
;  CF - 1 error initalizing sound blaster
;  CF - 0 ok!
;   EAX = amount EDX was adjusted because of DMA page overlap. eg: add _lomembase,ebx
;         this includes the original DMA size (0 if GUS)
;   EBX = 1 (can be used for determining if memory required on cpu, GUS returns 0,eg: imul ebx,200000)
;
; Notes:
;  If EAX < or = EDX, samples AND volume tables will follow DMA memory.
;  Volume tables use 16*256 bytes of memory - make sure you remember this
;  when allocating memory for samples.  200000 bytes of sample memory req'd
;  means to allocate 204096 bytes of memory.
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_sb_init:
         pushad
         mov ax,900h
         int 31h
         push ax

         mov [esp+28+2],ebx                 ; mov eax,ebx after final popad
         mov _sbvoltables,eax
         mov _sbdmabuffer,edx
         mov _sbdmasize,ebx
         mov _sbmixspeed,cx
         add edx,_code32a

         mov eax,edx                        ; make sure actual DMA address+size does not
         add eax,ebx                        ; cross a 64k page boundary
         cmp ax,dx
         ja sb_dmatalk
         dec edx
         xor dx,dx
         add edx,00010000h
         sub edx,_code32a
         mov eax,_sbdmabuffer
         sub eax,edx
         neg eax
         add eax,ebx
         mov [esp+28+2],eax
         mov _sbdmabuffer,edx
         add edx,_code32a

sb_dmatalk:
         mov ecx,ebx
         dec ecx

         mov al,05h                         ; program DMA controller
         out 0ah,al

         xor al,al
         out 0ch,al

         mov al,dl                          ; edx = location for DMA
         out 02h,al
         shr edx,8
         mov al,dl
         out 02h,al
         shr edx,8
         mov al,dl
         out 83h,al

         mov al,cl                          ; cx = length
         out 03h,al
         mov al,ch
         out 03h,al
         mov al,01h                         ; high length
         out 0ah,al

         mov al,59h
         out 0bh,al

         mov bl,_sbirq                      ; get protected mode IRQ going
         cmp bl,2
         jne short $+4
         mov bl,9
         cmp bl,7
         seta al
         movzx eax,al
         mov ax,irqm0tbl[eax*2]
         mov irqm00,ax
         mov edx,offset sb_irq
         call _setirqvect
         xor al,al
         call _setirqmask
         mov edi,offset rmsbirqbuf
         call _rmpmirqset
         mov ormsbirqvect,eax

         mov edi,_sbdmabuffer               ; clear DMA buffer
         mov al,80h
         mov ecx,_sbdmasize
         cld
         rep stosb

sb_resetdsp:
         mov dx,_sbport                     ; make sure SB is awake and listening
         add dx,06h
         mov al,1
         out dx,al
         in al,dx
         in al,dx
         in al,dx
         in al,dx
         in al,dx
         in al,dx
         in al,dx
         in al,dx
         xor al,al
         out dx,al
         mov cx,100
sb_waitid:
         mov dx,_sbport
         add dx,0eh
         in al,dx
         or al,al
         js sb_getid
         loop sb_waitid

         stc
         jmp sb_error

sb_getid:
         mov dx,_sbport
         add dx,0ah
         in al,dx
         cmp al,0aah
         je sb_sbok
         loop sb_waitid

         stc
         jmp sb_error

sb_sbok:
         mov dx,_sbport
         add dx,0ch
         mov ax,1000
         mul ax
         div _sbmixspeed
         neg al
         mov ah,al
         mov dx,_sbport
         add dx,0ch
         sbout 40h                         ; 40h = set digitized transfer time constant
         sbout ah

sb_startdma:
         mov ebx,_sbdmasize
         shr ebx,1
         dec ebx
         sbout 48h                          ; 48h = set DSP block transfer size
         sbout bl
         sbout bh
         sbout 1ch                          ; 1Ch = start auto-initialize mode
         sbout 0d1h                         ; D1h = turn sound blaster digitized sounds on

         mov ebx,_sbdmasize                 ; set pre-calculated DMA/2 size
         shr ebx,1
         mov dmahalf,ebx

         mov eax,_sbdmabuffer               ; set xor for chunk flipping
         add ebx,eax
         xor ebx,eax
         mov dmaxor,ebx
         mov dmachunk,eax
         xor dmachunk,ebx                   ; point active dma chunk to second half (while first half plays)

         cmp eax,_sbvoltables
         jb sb_oklocation

         add eax,_sbdmasize                 ; set volume table location
         mov _sbvoltables,eax
sb_oklocation:
         mov eax,_sbvoltables
         mov esi,eax

         add eax,16*256+256                 ; set location for virtual ram
         mov _sbsamples,eax

         mov ecx,16*256-1                   ; generate volume tables
         movzx ebx,_sbvolume                ; percentile volume diminishing so sample addition doesn't carry

sb_volloop:
         movzx eax,cl
         sub eax,80h
         movzx edi,ch
         imul edi
         shr eax,4                          ; shr eax,4 = full volume
         imul bx
         mov [esi+ecx],ah                   ; ah rather than shr eax,8
         dec ecx
         jnl sb_volloop

         mov ecx,256-1                      ; make master volume lookup tables
sb_mastertables:
         mov ebx,ecx
         mov eax,ecx
         and bl,0fh
         shr al,4
         imul ebx
         imul eax,17
         mov ebx,15*15
         idiv ebx
         cmp al,17
         jne sb_special
         mov al,0fh
sb_special:
         mov [esi+16*256+ecx],al
         dec ecx
         jnl sb_mastertables

         mov ecx,60
         xor ebx,ebx
sbpitchloop:
         movzx eax,rawfreq[ebx*2]
         call _sb_getfreq
         mov _freqtbl[ebx*2],ax

         inc ebx
         loop sbpitchloop

         pop ax
         int 31h
         popad
         mov ebx,1
         clc
         ret

sb_error:
         pop ax
         int 31h
         popad
         xor ebx,ebx
         xor eax,eax
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Unintialize SB
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_sb_uninit:
         pushad
         mov ax,900h
         int 31h
         push ax
         mov dx,_sbport
         add dx,0ch
         sbout 0d3h                         ; D3h = turn sound blaster digitized sounds off
         sbout 0dah                         ; DAh = exit auto-initialize mode

         mov bl,_sbirq                      ; restore original IRQ
         cmp bl,2
         jne short $+4
         mov bl,9
         mov eax,ormsbirqvect
         call _rmpmirqfree
         mov al,1
         call _setirqmask
         pop ax
         int 31h
         popad
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Shove some crap into SB virtual ram
; In:
;   EBX - addx in SB virtual ram to shove into
;   ECX - amount of crap to shove
;   EDX -> actual crap to shove
; Notes:
;  All incomming data will be XOR'ed with _sfxsign  !!!
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_sb_putram:
         pushad
         add ebx,_sbsamples
pr_loop:
         mov al,[edx]
         xor al,_sfxsign
         mov [ebx],al
         inc edx
         inc ebx
         loop pr_loop
         popad
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Rip some crap outta SB virtual ram
; In:
;   EBX - addx in SB virtual ram to rip from
;   ECX - amount of crap to rip
;   EDX -> buffer for ripped crap
; Notes:
;  All outgoing data will be XOR'ed with _sfxsign  !!!
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_sb_getram:
         pushad
         add ebx,_sbsamples
gr_loop:
         mov al,[ebx]
         xor al,_sfxsign
         mov [edx],al
         inc edx
         inc ebx
         loop gr_loop
         popad
         ret

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Main loop - Handle sound blaster mixing/voices/looping/bidirectional/volume
;
; This routine simply emulates a GUS on crappy Sound Blaster.
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_sb_handler:
         movzx ebp,_sfxnumvoices
         dec ebp

_sb_moreloop:
         mov sbebp,ebp

         test _vccmnd[ebp],shutup
         jz short sbh_0f9
         mov edi,ebp
         call _sb_shutup
         jmp sbh_next
sbh_0f9:

         test _vccmnd[ebp],vol
         jz sbh_more3
         mov al,_vcvol[ebp]
         mov acvol[ebp],al
sbh_more3:

         test _vccmnd[ebp],freq
         jz sbh_more0
         mov ax,_vcfreq[ebp]
         mov acfreq[ebp*2],ax
sbh_more0:

         test _vccmnd[ebp],play
         jz sbh_more1

         mov al,_vccntrl[ebp]
         mov accntrl[ebp],al
         mov eax,_vclbeg[ebp*4]
         add eax,_sbsamples
         mov aclbeg[ebp*4],eax
         mov edx,_vcsbeg[ebp*4]
         add edx,_sbsamples
         mov eax,_vclend[ebp*4]
         add eax,_sbsamples
         mov aclend[ebp*4],eax
         mov acpos[ebp*4],edx
         mov acstep[ebp],0
         mov bh,_vcvol[ebp]
         mov acvol[ebp],bh
         mov ax,_vcfreq[ebp]
         mov acfreq[ebp*2],ax
         mov accmnd[ebp],cont
         mov _vccmnd[ebp],0
         jmp short sbh_mixit

sbh_more1:
         test accmnd[ebp],cont
         jz sbh_next

sbh_mixit:
         mov edx,acpos[ebp*4]               ; get actual sample position
         xor ebx,ebx                        ; clear top word
         mov bl,acvol[ebp]                  ; get voice volume
         shl bl,4
         or bl,_mix_volume[ebp]             ; get channel volume
         xor eax,eax
         mov ax,acfreq[ebp*2]               ; get voice frequency scaling
         xor ecx,ecx
         test accntrl[ebp],loop_rev         ; set Z flag of looping
         mov cl,ah                          ; transfer high step temporarily
         mov ah,acstep[ebp]                 ; get step from last time through
         mov edi,aclend[ebp*4]              ; get sample end
         mov ebp,ecx                        ; set high step for sample increment
         mov samend,edi                     ; dont have an extra register, must save in memory
         mov ecx,dmahalf                    ; set loop counter
         mov edi,dmachunk                   ; find current DMA chunk (front/back half)
         mov esi,_sbvoltables               ; point to volume tables
         mov bh,byte ptr [esi+16*256+ebx]   ; mix in master volume for channel
         jnz sbh_subloop                    ; use Z flag from looping

sbh_addloop:
         mov bl,[edx]                       ; mixing loop, get from sample
         mov bl,[ebx+esi]                   ; volume cross referance, bh = 0 - 15, bl = byte
         add [edi],bl                       ; mix into DMA
         inc edi                            ; inc DMA location
         add ah,al                          ; increment fractional counter
         adc edx,ebp                        ; increment integer counter
         cmp samend,edx                     ; done sample?
         jbe short sbh_firstshot            ; yes, check for looping else exit
         dec ecx                            ; count, dec ecx is faster than loop
         jnz short sbh_addloop

         mov ebp,sbebp                      ; if cpu gets here, sample continues to next DMA chunk
         mov acpos[ebp*4],edx               ; save data from this transfer to use in next
         mov acstep[ebp],ah
         jmp sbh_next

sbh_firstshot:
         mov saveebp,ebp                    ; in case of looping, all registers must remain same
         mov ebp,sbebp
         test accntrl[ebp],loop_on
         jnz sbh_handlelooping

         mov ebp,sbebp                      ; get voice number
         mov accmnd[ebp],0                  ; voice done, clear command
         mov _vccmnd[ebp],0                 ; clear virtual command
         jmp sbh_next                       ; do next voice

sbh_subloop:
         mov bl,[edx]                       ; backwards mixing loop:
         mov bl,[ebx+esi]                   ; volume cross referance, bh = 0 - 15, bl = byte
         add [edi],bl                       ; mix into DMA
         inc edi                            ; inc DMA location
         add ah,al                          ; increment fractional counter
         sbb edx,ebp                        ; decrement integer counter this time
         cmp edx,samend                     ; done sample?
         jbe short sbh_firstshot            ; yes, check for looping else exit
         dec ecx                            ; count, dec ecx is faster than loop
         jnz short sbh_subloop

         mov ebp,sbebp                      ; if cpu gets here, sample continues to next DMA chunk
         mov acpos[ebp*4],edx               ; save data from this transfer to use in next
         mov acstep[ebp],ah
         jmp sbh_next

sbh_handlelooping:
         test accntrl[ebp],loop_bi
         jz short sbh_nobidir
         xor accntrl[ebp],loop_rev          ; flip direction of loop
         push eax ebx
         mov eax,aclend[ebp*4]
         mov ebx,aclbeg[ebp*4]
         mov aclend[ebp*4],ebx
         mov aclbeg[ebp*4],eax
         mov samend,ebx
         pop ebx eax
sbh_nobidir:
         mov edx,aclbeg[ebp*4]
         test accntrl[ebp],loop_rev
         jnz sbh_subtest
         mov ebp,saveebp
         dec ecx                            ; count, dec ecx is faster than loop
         jnz sbh_addloop

sbh_next:
         mov ebp,sbebp
         dec ebp
         jnl _sb_moreloop

         ret

sbh_subtest:
         mov ebp,saveebp                    ; backwards part of bi-directional loop
         dec ecx
         jnz sbh_subloop
         jmp short sbh_next

;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
; Convert actual frequency to SB frequency number
; In:
;   EAX - frequency
; Out:
;   AX - SB frequency scaling number
;   EAX high word - ?
;░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

_sb_getfreq:
         push ebp ebx
         mov ebp,eax
         mov ax,_sfxmiddlec                 ; make pitch tables
         shl eax,16                         ; step[ebx]="middle c" * rawfreq[ebx] / mixrate / rawfreq[24*2]
         movzx ebx,_sbmixspeed
         cdq
         div ebx
         mul ebp

         mov ebp,8664
         div ebp
         shr eax,8

         pop ebx ebp
         ret

code32   ends
         end

[ RETURN TO DIRECTORY ]