;*	WSS.ASM
;*
;* Windows Sound System Sound Device
;*
;* $Id: wss.asm,v 1.2 1996/08/04 11:35:36 pekangas Exp $
;*
;* Copyright 1996 Petteri Kangaslampi and Jarno Paananen
;*
;* This file is part of the MIDAS Sound System, and may only be
;* used, modified and distributed under the terms of the MIDAS
;* Sound System license, LICENSE.TXT. By continuing to use,
;* modify or distribute this file you indicate that you have
;* read the license and understand and accept it fully.
;*



IDEAL
P386
JUMPS

INCLUDE "lang.inc"
INCLUDE "errors.inc"
INCLUDE "sdevice.inc"
INCLUDE "mixsd.inc"
INCLUDE "dsm.inc"


;/***************************************************************************\
;*       enum wssFunctIDs
;*       ----------------
;* Description:  ID numbers for WSS Sound Device functions
;\***************************************************************************/

enum    wssFunctIDs \
        ID_wssDetect = ID_wss, \
        ID_wssInit, \
        ID_wssClose



DATASEG

D_int   wssRate                         ; WSS actual output rate

wssSpd		DB	?		; WSS speed value
wssOLMute	DB	?		; WSS old left channel mute value
wssORMute	DB	?		; WSS old right channel mute value


IFDEF __PASCAL__
EXTRN   WSS : SoundDevice
ENDIF




IDATASEG


WSSCONFIGBITS = sdUsePort or sdUseIRQ or sdUseDMA or sdUseMixRate or \
                sdUseOutputMode or sdUseDSM
WSSMODEBITS = sdMono or sdStereo or sd8bit or sd16bit

; If compiling for Pascal, Sound Device name is wssSD, from which the data
; will be copied to Sound Device WSS, defined in Pascal.

IFDEF   __PASCAL__
SDNAM           equ     wssSD
ELSE
SDNAM           equ     WSS
ENDIF

GLOBAL  SDNAM : SoundDevice

SDNAM   SoundDevice     < \
 0,\
 WSSCONFIGBITS,\
 530h, 9, 3,\
 1, 1,\
 WSSMODEBITS,\
 ptr_to wssSDName,\
 ptr_to wssCardNames,\
 4, ptr_to wssPortAddresses,\
 ptr_to wssDetect,\
 ptr_to wssInit,\
 ptr_to wssClose,\
 ptr_to dsmGetMixRate,\
 ptr_to mixsdGetMode,\
 ptr_to mixsdOpenChannels,\
 ptr_to dsmCloseChannels,\
 ptr_to dsmClearChannels,\
 ptr_to dsmMute,\
 ptr_to dsmPause,\
 ptr_to dsmSetMasterVolume,\
 ptr_to dsmGetMasterVolume,\
 ptr_to mixsdSetAmplification,\
 ptr_to mixsdGetAmplification,\
 ptr_to dsmPlaySound,\
 ptr_to dsmReleaseSound\
 ptr_to dsmStopSound,\
 ptr_to dsmSetRate,\
 ptr_to dsmGetRate,\
 ptr_to dsmSetVolume,\
 ptr_to dsmGetVolume,\
 ptr_to dsmSetSample,\
 ptr_to dsmGetSample,\
 ptr_to dsmSetPosition,\
 ptr_to dsmGetPosition,\
 ptr_to dsmGetDirection,\
 ptr_to dsmSetPanning,\
 ptr_to dsmGetPanning,\
 ptr_to dsmMuteChannel,\
 ptr_to dsmAddSample,\
 ptr_to dsmRemoveSample,\
 ptr_to mixsdSetUpdRate,\
 ptr_to mixsdStartPlay,\
 ptr_to mixsdPlay >


LABEL	wssRates	WORD		; sampling rates for WSS
	DW	 8000, 00h
	DW	 5513, 01h
	DW	16000, 02h
	DW	11025, 03h
	DW	27429, 04h
	DW	18900, 05h
	DW	32000, 06h
	DW	22050, 07h
	DW	    0, 08h		; not supported
	DW	37800, 09h
	DW	    0, 0Ah
	DW	44100, 0Bh
	DW	48000, 0Ch
	DW	33075, 0Dh
	DW	 9600, 0Eh
	DW	 6615, 0Fh


wssSDName       DB      "Windows Sound System Sound Device v1.30", 0

wssCardNames    DD      ptr_to wssName

wssName         DB      "Windows Sound System (Crystal/Analog CODEC)", 0

IFDEF __16__
wssPortAddresses  DW    530h, 604h, 0E80h, 0F40h
ELSE
wssPortAddresses  DD    530h, 604h, 0E80h, 0F40h
ENDIF



CODESEG



;/***************************************************************************\
;*
;* Function:    WaitCODEC
;*
;* Description: Waits until the WSS CODEC finishes initializing
;*
;* Returns:	carry set if error, otherwise carry clear
;*
;* Destroys:	ax, cx, dx
;*
;\***************************************************************************/

PROC NOLANGUAGE WaitCODEC	NEAR	; waits until CODEC finishes
					;  initializing. Carry set if error
	clc

        mov     _dx,[WSS.port]          ; dx = CODEC Index Address Register
        add     _dx,4

        mov     _cx,8000h

@@wait: in	al,dx
	or	al,al			; wait until bit 7 is zero or 2000h
	jns	@@ok			; reads
	loop	@@wait

	stc				; if read 2000h times, there is a
					; problem with the CODEC
@@ok:	ret

ENDP




;/***************************************************************************\
;*
;* Function:    int wssDetect(int *result);
;*
;* Description: Detects Windows Sound System soundcard
;*
;* Returns:     MIDAS error code.
;*              1 stored to *result if WSS was detected, 0 if not.
;*
;\***************************************************************************/

PROC    wssDetect       _funct  result : _ptr
USES    _bx

        LOADPTR es,_bx,[result]         ; store 0 in *result - no detection
        mov     [_int _esbx],0

;FIXME!!!

        xor     _ax,_ax
	ret
ENDP




;/***************************************************************************\
;*
;* Function:    int wssInit(unsigned mixRate, unsigned mode);
;*
;* Description: Initializes Windows Sound System
;*
;* Input:       unsigned mixRate        mixing rate
;*              unsigned mode           output mode (see enum sdMode)
;*
;* Returns:     MIDAS error code
;*
;\***************************************************************************/

PROC    wssInit         _funct  mixRate : _int, mode : _int
USES    _si,_di,_bx
LOCAL   wssMode : _int

	mov	[wssMode],0

	test	[mode],sd8bit		; force 8-bit?
	jnz	@@8b
	or	[wssMode],sd16bit	; if not, use 16 bits
	jmp	@@bit
@@8b:	or	[wssMode],sd8bit

@@bit:	test	[mode],sdMono		; force mono?
	jnz	@@mono
	or	[wssMode],sdStereo	; if not, use stereo
	jmp	@@mst
@@mono: or	[wssMode],sdMono

@@mst:
        mov     _dx,[WSS.port]
        add     _dx,4                   ; dx = CODEC Index Address Register
	in	al,dx			; is the CODEC busy?
	or	al,al
        jns     @@notbusy

        mov     _ax,errSDFailure        ; CODEC busy - failure
        jmp     @@err

@@notbusy:
IF 0
	mov	al,0Ch			; select misc. register
	out	dx,al
        inc     _dx
	in	al,dx			; AL = CODEC version

	mov	bl,al

	xor	al,al
	out	dx,al			; write 0 to misc. register
	in	al,dx			; and read it back
	cmp	al,bl			; if value changed this is not a
        je      @@codecok               ; CODEC

        mov     _ax,errSDFailure        ; value changed - not a CODEC
        jmp     @@err
ENDIF

@@codecok:
        mov     _bx,[WSS.IRQ]
	cmp	bl,7
	je	@@IRQ7
	cmp	bl,9
	je	@@IRQ9
	cmp	bl,10
	je	@@IRQ10
	cmp	bl,11
	je	@@IRQ11

        mov     _ax,errSDFailure        ; invalid IRQ number
        jmp     @@err

@@IRQ7: mov	al,08h			; IRQ value for CODEC configuration
	jmp	@@IRQd

@@IRQ9: mov	al,10h			; IRQ value for CODEC configuration
	jmp	@@IRQd

@@IRQ10:
	mov	al,18h			; IRQ value for CODEC configuration
	jmp	@@IRQd

@@IRQ11:
	mov	al,20h			; IRQ value for CODEC configuration


@@IRQd:
        mov     _bx,[WSS.DMA]
	cmp	bl,0
	je	@@DMA0
	cmp	bl,1
	je	@@DMA1
	cmp	bl,3
	je	@@DMA3

        mov     _ax,errSDFailure        ; invalid DMA number
        jmp     @@err

@@DMA0: or	al,01h			; DMA value for CODEC configuration
	jmp	@@DMAd

@@DMA1: or	al,02h			; DMA value for CODEC configuration
	jmp	@@DMAd

@@DMA3: or	al,03h			; DMA value for CODEC configuration

@@DMAd: mov     _dx,[WSS.port]
	out	dx,al			; set IRQ and DMA numbers

	; WSS does _NOT_ seem to use any interrupts if using autoinit
	; DMA, so setting a IRQ-handler is unnecessary.

	; now search for closest match of the mixing rate from the wssRates
	; table

        mov     _cx,16                  ; 16 possible values
        xor     _si,_si                 ; pointer to rate table
        mov     _dx,32767               ; distance from best match
        xor     _bx,_bx                 ; rate number for best match

@@rl:
IFDEF __32__
        xor     eax,eax
ENDIF
        mov     ax,[wssRates+_si]       ; get a rate from table
        add     _si,2
        sub     _ax,[mixRate]           ; distance from wanted mixing rate
	js	@@1			; if this rate is smaller, ignore
        cmp     _ax,_dx                 ; is distance greater than best match?
	jae	@@1			; if is, ignore

        mov     bx,[wssRates+_si]       ; rate number for this match
        mov     _dx,_ax                 ; distance

@@1:    add     _si,2                   ; next rate
	loop	@@rl

	mov	[wssSpd],bl		; store rate number
        shl     _bx,2
IFDEF __32__
        xor     eax,eax
ENDIF
        mov     ax,[wssRates+_bx]       ; get actual mixing rate from table
        mov     [wssRate],_ax           ; store actual mixing rate


        ; Take care of common initialization for all mixing Sound Devices:
        push    es
        call    mixsdInit LANG, [wssRate], [wssMode], [WSS.DMA]
        pop     es
        test    _ax,_ax
        jnz     @@err


        mov     _dx,[WSS.port]
        add     _dx,4
	mov	al,0Ah
	out	dx,al
        inc     _dx
	in	al,dx			; external mute on
	or	al,40h
	out	dx,al

        mov     _cx,1200h               ; delay to prevent clicks (value from
					;  CODEC.ASM, ExtMute, WSS SDK 1.0)
@@w1:	in	al,84h			; a "safe" I/O port
	loop	@@w1


        ; Write zero to Pin Control (why?)
        mov     _dx,[WSS.port]
        add     _dx,4
        mov     al,0Ah
        out     dx,al
        inc     _dx
        xor     al,al
        out     dx,al


        ; Set single DMA mode and start autocalibration:

        mov     _dx,[WSS.port]
        add     _dx,4
        mov     al,49h                  ; enable MCE
	out	dx,al
        inc     _dx
	mov	al,04h or 08h		; single DMA channel, enable
	out	dx,al			; autocalibration

        ; Maybe we should do the port reading again? Shouldn't hurt anyone
        ; anyhow:

        in      al,dx
        in      al,dx


        ; Now set playing rate and output format:

        mov     _dx,[WSS.port]
        add     _dx,4                   ; enable MCE and select Clock and
	mov	al,48h			; Data Format Register (08h)
	out	dx,al

        inc     _dx
	mov	al,[wssSpd]		; Clock Frequency Source & Divide
	test	[wssMode],sd16bit
	jz	@@no16
	or	al,40h			; 16-bit signed linear (0 - 8-bit
					; unsigned linear)
@@no16:
	test	[wssMode],sdStereo	; stereo?
	jz	@@nostereo
	or	al,10h			; if yes, set stereo bit 1

@@nostereo:
	out	dx,al

        ; Now according to GUS MAX SDK source we need to read from the CODEC
        ; data port twice. Don't ask me why, but I really hope this solves
        ; the problem in setting the sample rate...

        in      al,dx
        in      al,dx

        ; Wait for CODEC initialization to complete:
        call    WaitCODEC
        jnc     @@initok
        mov     _ax,errSDFailure
        jmp     @@err

@@initok:
        ; Now disable MCE:

        mov     _dx,[WSS.port]
        add     _dx,4

@@disablemce:
	mov	al,08h			; disable MCE
	out	dx,al
        jmp short $+2
        in      al,dx                   ; according to the GUS MAX SDK we need
        cmp     al,08h                  ; to check the out got through - maybe
        jne     @@disablemce            ; a bug in the CODEC?


        ; Delay to ensure autocalibration has started:
        mov     _cx,10000
@@delay:
        in      al,84h
        loop    @@delay


        ; Now we need to wait until autocalibration is finished. First check
        ; that we actually do get the Test and Initialization register
        ; number written to the index: (Again from GUS MAX SDK)

@@testinit:
        mov     al,0Bh
        out     dx,al
        jmp short $+2
        in      al,dx
        cmp     al,0Bh
        jne     @@testinit

        ; Now wait until bit 5 becomes zero in the register:
@@wacal:
        mov     al,0Bh
        out     dx,al
        inc     _dx                     ; wait until autocalibration is
        in      al,dx                   ; finished
        dec     _dx
        test    al,32
        jnz     @@wacal


        ; Set Single DMA channel mode, disable autocalibration:
        mov     _dx,[WSS.port]
        add     _dx,4
        mov     al,49h
        out     dx,al
        inc     _dx
        mov     al,04h
        out     dx,al

        ; Disable MCE:
        dec     _dx
        xor     al,al
        out     dx,al


        mov     _cx,1200h               ; delay to prevent clicks (value from
					;  CODEC.ASM, ExtMute, WSS SDK 1.0)
@@w2:	in	al,84h			; a "safe" I/O port
	loop	@@w2

        mov     _dx,[WSS.port]
        add     _dx,4
	mov	al,0Ah
	out	dx,al
        inc     _dx                     ; external mute off
	in	al,dx
	and	al,NOT 40h
	out	dx,al


        mov     _dx,[WSS.port]
        add     _dx,6                   ; acknowledge CODEC interrupt (just
	xor	al,al			;  for safety...)
	out	dx,al


        mov     _dx,[WSS.port]
        add     _dx,4                   ; select the lower base count
	mov	al,0Fh
	out	dx,al
        inc     _dx
	mov	al,255			; set the low byte of count (DMAC
	out	dx,al			; takes care of wrapping)

        dec     _dx
	mov	al,0Eh			; select the upper base count
	out	dx,al
        inc     _dx
	mov	al,255			; set the high byte of count
	out	dx,al

        ; Write 0 to Pin Control register: (?)
        dec     _dx
        mov     al,0Ah
        out     dx,al
        inc     _dx
        xor     al,al
        out     dx,al

        mov     _dx,[WSS.port]
        add     _dx,4                   ; write to the Interface Configuration
	mov	al,09h
	out	dx,al

        inc     _dx
	mov	al,05h			; use DMA playback
	out	dx,al

        dec     _dx
	mov	al,06h
	out	dx,al			; mute off from left channel
        inc     _dx
	in	al,dx
	mov	[wssOLMute],al
	and	al,NOT 128
	out	dx,al

        dec     _dx
	mov	al,07h
	out	dx,al			; mute off from right channel
        inc     _dx
	in	al,dx
	mov	[wssORMute],al
	and	al,NOT 128
	out	dx,al

@@ok:
        xor     _ax,_ax                 ; WSS succesfully initialized
        jmp     @@done


@@err:  ERROR   ID_wssInit

@@done:
	ret
ENDP




;/***************************************************************************\
;*
;* Function:    int wssClose(void)
;*
;* Description: Uninitializes Windows Sound System
;*
;* Returns:     MIDAS error code
;*
;\***************************************************************************/

PROC    wssClose        _funct
USES    _bx

        mov     _dx,[WSS.port]
        add     _dx,4

	mov	al,06h
	out	dx,al			; old mute setting to left channel
        inc     _dx
	mov	al,[wssOLMute]
	out	dx,al

        dec     _dx
	mov	al,07h
	out	dx,al			; old mute setting to right channel
        inc     _dx
	mov	al,[wssORMute]
	out	dx,al

        dec     _dx                     ; Pin Control Register
	mov	al,0Ah
	out	dx,al
        inc     _dx
	xor	al,al			; turn off interrupts
	out	dx,al

        inc     _dx
	out	dx,al			; acnowledge outstanding interrupts

        sub     _dx,2
	mov	al,09h			; Interface Configuration Register
	out	dx,al
        inc     _dx
	xor	al,al			; turn off CODEC's DMA
	out	dx,al

        ; Take care of common uninitialization for all mixing Sound Devices:
        push    es
        call    mixsdClose
        pop     es
        test    _ax,_ax
        jnz     @@err

        xor     _ax,_ax
        jmp     @@done


@@err:  ERROR   ID_wssClose

@@done:
	ret
ENDP


;* $Log: wss.asm,v $
;* Revision 1.2  1996/08/04 11:35:36  pekangas
;* All functions now preserve _bx
;*
;* Revision 1.1  1996/05/22 20:49:33  pekangas
;* Initial revision
;*

END