;*	WSS.ASM
;*
;* Windows Sound System Sound Device, v1.22
;*
;* Copyright 1995 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 "dsm.inc"
INCLUDE "dma.inc"


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

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



DATASEG

wssSpd		DB	?		; WSS speed value
wssRate 	DW	?		; WSS actual playing rate
wssOLMute	DB	?		; WSS old left channel mute value
wssORMute	DB	?		; WSS old right channel mute value
wssDMABuffer    dmaBuffer       ?       ; DMA playing buffer
wssDMAPos       DW      ?               ; DMA playing position


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, \
 sdUnInitialized, \
 WSSMODEBITS, \
 far ptr wssSDName, \
 far ptr wssCardNames, \
 4, far ptr wssPortAddresses, \
 far ptr wssDetect, \
 far ptr wssInit, \
 far ptr wssClose, \
 far ptr dsmGetMixRate, \
 far ptr dsmGetMode, \
 far ptr dsmOpenChannels, \
 far ptr dsmCloseChannels, \
 far ptr dsmClearChannels, \
 far ptr dsmMute, \
 far ptr dsmPause, \
 far ptr dsmSetMasterVolume, \
 far ptr dsmGetMasterVolume, \
 far ptr dsmSetAmplification, \
 far ptr dsmGetAmplification, \
 far ptr dsmPlaySound, \
 far ptr dsmStopSound, \
 far ptr dsmSetRate, \
 far ptr dsmGetRate, \
 far ptr dsmSetVolume, \
 far ptr dsmGetVolume, \
 far ptr dsmSetInstrument, \
 far ptr dsmGetInstrument, \
 far ptr dsmSetPosition, \
 far ptr dsmGetPosition, \
 far ptr dsmSetPanning, \
 far ptr dsmGetPanning, \
 far ptr dsmMuteChannel, \
 far ptr dsmAddInstrument, \
 far ptr dsmRemInstrument, \
 far ptr dsmSetUpdRate, \
 far ptr wssStartPlay, \
 far ptr dsmPlay >


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.22", 0

wssCardNames    DD      far ptr wssName

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

wssPortAddresses  DW    530h, 604h, 0E80h, 0F40h



CODESEG



PUBLIC	wssDetect
PUBLIC	wssInit
PUBLIC	wssClose



;/***************************************************************************\
;*
;* 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,2000h

@@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       FAR     result : dword

        les     bx,[result]             ; store 0 in *result - no detection
        mov     [word es:bx],0

        xor     ax,ax
	ret
ENDP




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

PROC	wssInit 	FAR	mixRate : word, mode : word
USES	si,di
LOCAL	wssMode : word

	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:
	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

@@codecok:
	mov	bl,[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	bl,[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:	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
	mov	ax,[wssRates+bx]	; get actual mixing rate from table
	mov	[wssRate],ax		; store actual mixing rate

        ; Calculate required mixing buffer size: (1/25th of a second)
        mov     ax,[wssRate]            ; ax = number of elements per second
        mov     bx,DMABUFLEN
        xor     dx,dx
        div     bx                      ; ax = number of elements in buffer

        mov     cx,[wssMode]            ; 16-bit output mode?
        test    cx,sd16bit
        jz      @@n16b                  ; if yes, multiply buffer length by
        shl     ax,1                    ; two - elements are two bytes

@@n16b:
        test    cx,sdStereo             ; stereo output?
        jz      @@nstb                  ; if yes, multiply buffer length by
        shl     ax,1                    ; two - double the number of elements
                                        ; compared to mono
@@nstb:
        add     ax,16                   ; make buffer length a multiple of 16
        and     ax,0FFF0h

        ; ax is now the DMA buffer length - allocate DMA buffer:
        call    dmaAllocBuffer LANG, ax, seg wssDMABuffer offset wssDMABuffer
        test    ax,ax                   ; buffer allocated succesfully?
        jnz     @@err

        mov     es,[wssDMABuffer.bsegment]
        xor     bx,bx

        ; Initialize Digital Sound Mixer using the correct mixing rate and
        ; output mode, mixing to the newly allocated buffer:
        call    dsmInit LANG, [wssRate], [wssMode], es bx, \
                [wssDMABuffer.blength]
        test    ax,ax                   ; error initializing DSM?
        jnz     @@err

        ; Start playing the DMA buffer:
        movzx   ax,[WSS.DMA]
	mov	bx,1			; use auto-initialization
        call    dmaPlayBuffer LANG, seg wssDMABuffer offset wssDMABuffer, \
                ax, bx
        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


	mov	si,2

	; For some unknown reason this has to be done twice. Don't ask me
	; why. Apparently something isn't initialized quite as it should
	; be, but this seems to work fine this way.

@@ratelp:
	call	WaitCODEC

	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

	call	WaitCODEC

	dec	si			; do it again...
	jnz	@@ratelp

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

	call	WaitCODEC

	mov	dx,[WSS.port]
	add	dx,4
	mov	al,08h			; disable MCE
	out	dx,al

@@wacal:
	mov	dx,[WSS.port]
	add	dx,4
	mov	al,11
	out	dx,al			; wait until autocalibration is
	inc	dx			; finished
	in	al,dx
	test	al,32
	jnz	@@wacal


	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


	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:
	mov	[WSS.status],sdOK
        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	FAR

        cmp     [WSS.status],sdOK
        je      @@sok

        mov     ax,errSDFailure
        jmp     @@err

@@sok:
	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

	movzx	ax,[WSS.DMA]
	call	dmaStop LANG, ax	; stop DMA playing
        test    ax,ax
        jnz     @@err

	call	dsmClose LANG		; uninitialize DSM
        test    ax,ax
        jnz     @@err

        ; Deallocate DMA buffer:
        call    dmaFreeBuffer LANG, seg wssDMABuffer offset wssDMABuffer
        test    ax,ax
        jnz     @@err

        mov     [WSS.status],sdUnInitialized

        xor     ax,ax
        jmp     @@done


@@err:  ERROR   ID_wssClose

@@done:
	ret
ENDP




;/***************************************************************************\
;*
;* Function:     int wssStartPlay(void);
;*
;* Description:  Reads DMA buffer position to dsmDMAPos. MUST be called ONCE
;*               each time before calling loop of SoundDevice.Play() and
;*               ModulePlayer.Play();
;*
;* Returns:      MIDAS error code
;*
;\***************************************************************************/

PROC    wssStartPlay    FAR

        ; Get DMA playing position and store it to wssDMAPos:
        call    dmaGetPos LANG, seg wssDMABuffer offset wssDMABuffer, \
                seg wssDMAPos offset wssDMAPos
        test    ax,ax
        jnz     @@err

        ; Give the new DMA playing position to DSM:
        call    dsmSetPlayPos LANG, [wssDMAPos]
        test    ax,ax
        jnz     @@err

        jmp     @@done

@@err:
        ERROR   ID_wssStartPlay

@@done:
        ret
ENDP





END
