/***************************************************************************
*	NAME:  PLAY16.C $Revision: 1.32 $
**	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."
****************************************************************************
* $Log: play16.c $
* Revision 1.32  1995/11/17 08:14:46  sdsmith
* Switched back to double buffering the DMA for wave playback.
* Revision 1.31  1995/11/10 15:01:53  sdsmith
* Changed for Win95 compatability
* Revision 1.30  1995/10/27 16:19:44  sdsmith
* Changes for Wave compat tests in win95
* Revision 1.29  1995/10/27 12:56:20  sdsmith
* Fixed Win95 position backwards and 66ms response
* Revision 1.28  1995/10/26 14:59:37  mleibow
* Added mutes for software mixer controls.
* Revision 1.27  1995/10/13 17:24:50  mleibow
* Changed master volume calls to do 10dB drop off instead of 6dB drop off.
* Revision 1.26  1995/09/29 15:58:46  sdsmith
* Made change to flush silence into CODEC at stop_digital
* Made change to DMA buffer prefill for Woodruff and the Schnibble
* Revision 1.25  1995/08/10 13:43:57  sdsmith
* Added voice use check in the pan routine
* Revision 1.24  1995/07/18 16:49:19  sdsmith
* Added fixes for AVI files.
* Revision 1.23  1995/07/13 18:08:51  sdsmith
* Revision 1.22  1995/07/13 16:50:24  sdsmith
* Think we fixed the multiple DMA_SPLIT
* Revision 1.21  1995/07/07 11:14:15  sdsmith
* FullInit fixes for software volumes
* Revision 1.20  1995/06/29 08:45:21  sdsmith
* Switched to push disable and pop for trials
* Revision 1.19  1995/06/22 08:30:50  sdsmith
* Added master volume query functions
* Revision 1.18  1995/06/01 00:49:13  sdsmith
* Fixed tracking variable init for multiple wave file playback
* Revision 1.17  1995/05/31 13:44:31  sdsmith
* Removed DMA fixup for good.
* Revision 1.16  1995/05/29 17:05:02  sdsmith
* DMA Fixes were borken now fixed but broke windows so I removed DMA
* fixed.
* Revision 1.15  1995/05/24 14:12:22  mleibow
* fixed syntax error
* Revision 1.14  1995/05/24 23:43:53  sdsmith
* Fixed wave client volume vs. wave master volume
* Revision 1.13  1995/05/17 01:49:42  sdsmith
* Fixed CODEC DAC volume interaction with digital audio master
* Revision 1.12  1995/05/15 22:55:09  sdsmith
* Revision 1.11  1995/05/15 22:20:45  sdsmith
* Fixed buffer sync protection for Windows only
* Revision 1.10  1995/05/12 02:16:28  sdsmith
* Added semaphore code
* Revision 1.9  1995/05/09 23:47:50  sdsmith
* Read DAC register before setting volume
* Revision 1.8  1995/05/08 21:21:46  sdsmith
* Put in self correction check
* Revision 1.7  1995/05/08 07:32:28  sdsmith
* Fixed CODEC master volume
* Revision 1.6  1995/05/08 07:15:59  sdsmith
* Fixed windows support
* Revision 1.5  1995/05/03 13:40:06  unknown
* Removed unreferenced local varaiable
* Revision 1.4  1995/05/03 09:08:05  mleibow
* Added CODEC panning support
* Revision 1.3  1995/04/26 16:22:29  sdsmith
* Fixed the public function commentary
* Revision 1.2  1995/04/14 09:19:36  sdsmith
* Added support for B0 silicon
* Revision 1.1  1995/02/23 11:07:51  unknown
* Initial revision
***************************************************************************/

#include <stdlib.h>
#include <dos.h>
#if defined(__BORLANDC__)
#include <mem.h>
#elif defined(_MSC_VER)
#pragma warning (disable : 4704)
#include <memory.h>
#endif

#include "iw.h"
#include "iwl.h"
#include "digital.h"
#include "globals.h"
#include "codec.h"
#include "dma.h"

//#define DMA_SPLIT 4
#define STAT_STOPPING (STAT_STARTED|STAT_PAUSED)

#ifdef _MSC_VER
#pragma warning (disable : 4704)
#endif
#ifdef _WINDOWS
void far * _pascal MemCopySrc(void far *lpDst, void far *lpSrc, unsigned int cnt);
void far * _pascal MemCopyDst(void far *lpDst, void far *lpSrc, unsigned int cnt);
#define SEGINC 8u
// #define DEBUG
#ifdef DEBUG
void _pascal OutputDebugStr(char far *);
int column = 0;
extern char buffer[];
char *pointer = buffer;
#endif
#endif

extern UCHAR iwl_codec_flushed;

void dig_play_next_buffer_codec(int);
void iwl_codec_set_vol(short, short, int);
extern void iwl_zero_codec(void);

extern volatile struct iwl_dma_parms iwl_dma_parms[NUM_CHANNELS];
extern struct dig_voice_status codec_playback_voice;

static UCHAR last_buffer = 0;
static UCHAR user_pause = 0;
static UCHAR limit = 1;
static UCHAR RFAR *dma;
static ULONG transfer_size = 0;
static ULONG last_position = 0;
static ULONG in_handler = 0;
static unsigned int DMA_SPLIT = 0;
static UINT buffer_done = 0;
static short left_master = 0, right_master = 0;

/***************************************************************************

FUNCTION DEFINITION:
iwl_codec_setup_play_dma - setup DMA controller and CODEC for playback

DESCRIPTION:
iwl_codec_setup_play_dma checks if the virtual DMA channel to be used for
playback is free.  If so, this routine initializes the virtual DMA channel
data structure, programs the DMA controller, and programs the CODEC play
count.

NOTE: the virtual DMA channel is usually IW_DMA_CHAN_2.

RETURNS: int - IW_OK if operation was successful
               IW_DMA_BUSY if the virtual DMA channel is busy
*/
int iwl_codec_setup_play_dma(
  int play_dma_chan,                /* virtual DMA channel for playback */
  struct dig_voice_status RFAR *vs) /* CODEC playback voice data structure */
{
	struct iw_dma_buff RFAR *dma;

	dma = vs->pc_stbuff;

	if (!iw_dma_ready(play_dma_chan)) {
		return(IW_DMA_BUSY);
	}
	iwl_set_dma_active_state(play_dma_chan, 1);
	iwl_dma_parms[play_dma_chan].current_page = DMA_GET_PAGE(dma->paddr);
	iwl_dma_parms[play_dma_chan].current_addr = DMA_GET_ADDR(dma->paddr);
	iwl_dma_parms[play_dma_chan].size = vs->b_size;
	iwl_dma_parms[play_dma_chan].amount_to_xfer = vs->b_size;
	iwl_dma_parms[play_dma_chan].amount_xferred = 0;
	os_pgm_dma(play_dma_chan, iwl_dma_parms[play_dma_chan].size,
	  DMAMODE_SINGLE_MODE|DMAMODE_READ|DMAMODE_AUTOINIT,
	  DMA_GET_PAGE(dma->paddr),
	  DMA_GET_ADDR(dma->paddr));
	return(IW_OK);
}

/***************************************************************************

FUNCTION DEFINITION:
iwl_codec_start_play_dma - starts playback through the CODEC

DESCRIPTION:
iwl_codec_start_play_dma enables the CODEC to begin DMA transfer of 
digital audio data to the DACs on the codec.

NOTE: This routine should not be called until the DMA channel is 
      prepared with iwl_codec_setup_play_dma

RETURNS: void
*/
void iwl_codec_start_play_dma()
{
	OS_PUSH_DISABLE();
	iwl_cfig1i |= CODEC_PE;
	iwl_cfig2i &= ~CODEC_DAOF;
	IWL_CODEC_OUT(CONFIG_1, iwl_cfig1i);
	IWL_CODEC_OUT(CONFIG_2, iwl_cfig2i);
	OS_POP_FLAGS();
}

/***************************************************************************

FUNCTION DEFINITION:
iwl_codec_stop_play_dma - stops playback through the CODEC

DESCRIPTION:
iwl_codec_stop_play_dma shuts down the CODEC transfer of digital audio
data to the DACs.  This routine also stops all activity on the virtual
DMA channel used for CODEC playback.

RETURNS: void
*/
void iwl_codec_stop_play_dma()
{
	OS_PUSH_DISABLE();
	iwl_cfig1i &= ~CODEC_PE;
	iwl_cfig2i |= CODEC_DAOF;
	IWL_CODEC_OUT(CONFIG_1, iwl_cfig1i);
	IWL_CODEC_OUT(CONFIG_2, iwl_cfig2i);
	OS_POP_FLAGS();
}

/***************************************************************************

FUNCTION DEFINITION:
iwl_codec_program_play_count - setup of the CODEC play count

DESCRIPTION:
iwl_codec_program_play_count programs the number of bytes the CODEC will
transfer to the DACs before generating an interrupt.  The sample size
of the data and whether the data is stereo is taken into account when
the count is programmed.

RETURNS: unsigned int - actual play count programmed based on data format

SEE ALSO: 
dig_codec_play_next_buffer
*/
unsigned int iwl_codec_program_play_count(
  unsigned int size) /* number of bytes to transfer before interrupt */
{
	unsigned char temp;

	temp = iwl_cpdfi & 0xE0;	/* isolate the format bits */
	switch (temp) {
		case 0x40:		/* 16 bit litle endian */
		case 0xC0:		/* 16 bit big endian */
			size >>= 1;
			break;
		case 0xA0:		/* 16 bit ADPCM */
			size >>= 2;
			break;
	}

	if (iwl_cpdfi & CODEC_STEREO && temp != 0xA0) // not if ADPCM
		size >>= 1;

	size--;

	OS_PUSH_DISABLE();
	OS_OUTPORTB(iwl_codec_base,LOWER_PLAY_COUNT);
	OS_OUTPORTB(iwl_codec_data,(char)size);
	OS_OUTPORTB(iwl_codec_base,UPPER_PLAY_COUNT);
	OS_OUTPORTB(iwl_codec_data,(char)(size>>8));
	OS_POP_FLAGS();

	return(size);
}

/***************************************************************************

FUNCTION DEFINITION:
iw_codec_stop_digital - stop all CODEC playback functions

DESCRIPTION:
This routine first stops all playback activity through the codec.
Next the voice data structure used to track CODEC playback activity
is cleaned up.  During this process, any data buffers that have been
played, and any buffers that may have been in the process of being
played are returned to the wrapper or application with a
IW_DIG_BUFFER_DONE callback.

Once all buffers are returned to the wrapper and all activity to the
CODEC DACs has been stopped, we issue a IW_DIG_DONE callback to 
indicate to the wrapper that all digital processing has been completed.
The only way to restart playback after the wrapper receives IW_DIG_DONE
is with a call to iw_play_digital.

PARAMETERS:
	voice - identifies the codec voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_codec_play_digital).

RETURNS: void

SEE ALSO:
iwl_codec_stop_play_dma
*/
#ifdef __BORLANDC__
#pragma warn -par
#endif
void RFAR iw_codec_stop_digital(
  int voice) /* voice to stop playback on */
{
	struct dig_voice_status
	*vs;
	int i;
	UCHAR silence = 0;
	UCHAR status;

	OS_PUSH_DISABLE();
	vs = &codec_playback_voice;
	if (vs->status != STAT_UNUSED) {
		iwl_codec_stop_play_dma();
	        if (vs->type & IW_TYPE_8BIT) silence = 0x80;
		iwu_memset((void RFAR *)vs->pc_stbuff->vptr, (int)silence, (size_t)vs->pc_stbuff->size);
		iwl_codec_program_play_count((unsigned int)32);
		iwl_codec_start_play_dma();
		while (!((status = OS_INPORTB(iwl_codec_status)) & 0x01)) continue;
		iwl_codec_stop_play_dma();
		OS_OUTPORTB(iwl_codec_base, STATUS_3);
		OS_OUTPORTB(iwl_codec_data, ~CODEC_PFDI);
		os_stop_dma(IW_DMA_CHAN_2);
		iwl_set_dma_active_state(IW_DMA_CHAN_2, 0);
		vs->status = STAT_PAUSED;
		iwl_codec_flushed = 1;
		OS_PUSH_DS();
		if (vs->callback) {
			for (i=0; i < (int)DMA_SPLIT; i++) {
				if (vs->buffs[i].status & BUFSTAT_DONE) {
					for (; vs->buffs[i].dummy > 0; vs->buffs[i].dummy--) {
						OS_PUSH_DS();
						(*vs->callback)(IW_DIG_BUFFER_DONE, IW_CODEC_PLAYBACK_VOICE, 0,0);
						OS_POP_DS();
					}
				}
			}
	
			if (vs->size > 0)
				(*vs->callback)(IW_DIG_BUFFER_DONE, IW_CODEC_PLAYBACK_VOICE, 0,0);
			(*vs->callback)(IW_DIG_DONE, IW_CODEC_PLAYBACK_VOICE, 0,0);
		}
		OS_POP_DS();
		iwl_init_codec_voice(vs);
	}
	OS_POP_FLAGS();
}

/***************************************************************************

FUNCTION DEFINITION:
iw_codec_restart_digital - resume digital audio playback throug the CODEC

DESCRIPTION:
This funtion resumes playback of digital audio data after a call to
iw_codec_pause_digital.  This function should only be used to restart
after a call to iw_codec_pause_digital.

PARAMETERS:
	voice - identifies the codec voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_codec_play_digital).

RETURNS: void

SEE ALSO:
iwl_codec_start_play_dma
*/
void iw_codec_restart_digital(
	int voice)
{
	struct dig_voice_status
	*vs;

	vs = &codec_playback_voice;
	vs->status &= ~STAT_MASK;
	vs->status |= STAT_PLAYING;
	user_pause = 0;
	iwl_codec_enable_irqs();
	iwl_codec_start_play_dma();
}

/***************************************************************************

FUNCTION DEFINITION:
iw_codec_start_digital - start digital audio playback through the CODEC

DESCRIPTION:
This routine is normally used to begin playback when the wrapper
or application has preloaded data to be played.

This routine will set the voice status to playing then make sure there
is data to be played.  If no data has been sent, the wrapper or application
is sent IW_DIG_MORE_DATA callbacks to get data to be played.  Once there
is data to be played, the CODEC data transfer to the DACs is started.

PARAMETERS:
	voice - identifies the codec voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_codec_play_digital).

RETURNS: void
*/
void iw_codec_start_digital(
  int voice) /* voice on which to start playback */
{
	struct dig_voice_status
	*vs;

	vs = &codec_playback_voice;
	vs->type &= ~IW_TYPE_PRELOAD;
	vs->play_buf = 0;
	vs->position = 0;
	last_position = 0;
	in_handler = 0;
	iwl_codec_flushed = 0;
	iw_codec_restart_digital(voice);
}

/***************************************************************************

FUNCTION DEFINITION:
iw_codec_pause_digital - pause digital audio playback via the CODEC

DESCRIPTION:
iwl_codec_pause_playback shutsdown the CODEC's transfer of data to
the DACs.  This routine however, does not return empty buffers to the
wrapper or application.

PARAMETERS:
	voice - identifies the codec voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_record_digital).

RETURNS: void
*/
void iw_codec_pause_digital(
  int voice) /* voice to pause playback */
{
	struct dig_voice_status
	*vs;

    OS_PUSH_DISABLE();
	iwl_codec_stop_play_dma();
	if (!(iwl_cfig1i & CODEC_RE))
		iwl_codec_disable_irqs();
	user_pause = 1;

	vs = &codec_playback_voice;
	vs->status &= ~STAT_MASK;
	vs->status |= STAT_USER_PAUSED;
	OS_POP_FLAGS();
}
#ifdef __BORLANDC__
#pragma warn .par
#endif

/***************************************************************************

FUNCTION DEFINITION:
iw_codec_play_next_buffer - send a new buffer of digital audio data

DESCRIPTION:
iw_codec_play_next_buffer is used by the wrapepr or application to
send another buffer with digital audio data to be played.  If the 
current buffer is through being played the new buffer is accepted.  
If the CODEC is still playing data in the current buffer, then the
new buffer is rejected.

PARAMETERS:
	voice - identifies the codec voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_record_digital).
	pc_buffer - pointer to a buffer to receive the data sampled
	size      - size of the above buffer in bytes

RETURNS: int - IW_OK if new buffer was accepted
               IW_DMA_BUSY if new buffer was rejected
*/
int iw_codec_play_next_buffer(
  int voice,             /* voice to receive new pc data */
  UCHAR RFAR *pc_buffer, /* PC buffer containing new data to be played */
  ULONG size)            /* size of new PC buffer */
{
	struct dig_voice_status
	*vs = &codec_playback_voice;
	int rc;
	unsigned int i;

	OS_PUSH_DISABLE();
	if (vs->size == 0) {
	    vs->pc_buffer = pc_buffer;
	    vs->size = size;
	    last_buffer = 0;
	    for (i=0; i<DMA_SPLIT; i++) {
		if (vs->buffs[i].status == 0) {
		    dig_play_next_buffer_codec(limit*2);
		    break;
		}
	    }
	    if (vs->status & STAT_MASK == STAT_PLAYING)
		    iw_codec_restart_digital(voice);
	    rc = IW_OK;
	}
	else
		rc=IW_DMA_BUSY;
	OS_POP_FLAGS();
	return(rc);
}

/***************************************************************************

FUNCTION DEFINITION:
iw_codec_digital_position - retrieve number of bytes of data played

DESCRIPTION:
This routine returns the number of bytes of digital audio data that
have been played on the specified voice since the last call to
iw_codec_play_digital or iw_codec_start_digital.

PARAMETERS:
	voice - identifies the codec voice being used for playback.
			(this will be returned to the wrapper on the call to
			iw_record_digital).

RETURNS: ULONG - number of bytes played
*/
ULONG iw_codec_digital_position(
	int voice)
{
	struct dig_voice_status
	*vs = &codec_playback_voice;
	ULONG position;
	ULONG xferred;
	ULONG buffers;
	ULONG acct;
	int play_chan;


	//OS_PUSH_DISABLE();
	if (vs->status & STAT_PLAYING) {
	    do {
		buffers = in_handler;
		/*
		position = (in_handler >> 1) * vs->b_size;
		xferred = vs->b_size - os_dma_count(IW_DMA_CHAN_2);
		position += xferred;
		if (position < (in_handler * vs->max)) position += vs->b_size;
		*/
		    position = vs->position;
		    xferred = vs->b_size - os_dma_count(IW_DMA_CHAN_2);
		    acct = vs->max * vs->play_buf;
		    position += xferred;
		    if (xferred > acct) position -= acct;
		    else position += vs->b_size - acct;
	    } while (buffers != in_handler);
	}
	else {
	    position = vs->position;
	    //position = in_handler * vs->max;
	}
	if (position > last_position)
	    last_position = position;
	else
	    position = last_position;

	//OS_POP_FLAGS();
	return(position);
}

void iwl_dig_codec_master_vol(short left, short right, int use)
{
	struct dig_voice_status
	*vs = &codec_playback_voice;

	if (use) {
		left_master = (short)IWL_TO_FMM(iw_atten_tab[left]);
		right_master = (short)IWL_TO_FMM(iw_atten_tab[right]);
	}
	if ((vs->status & STAT_MASK) != STAT_UNUSED) 
	    iwl_codec_set_vol(0,0,0);
}

void iwl_dig_codec_get_master_vol(short *left, short *right)
{
    int i;

	for (i=0; i<IW_ATTENTABSIZE; i++) {
		if (IWL_FROM_FMM(left_master) > (short)iw_atten_tab[i]) {
			break;
		}
	}
	if (i)
		*left = i - 1;
	else
		*left = 0;
	if (*left < 0)
		*left = 0;
	if (*left > 127)
		*left = 127;
	for (i=0; i<IW_ATTENTABSIZE; i++) {
		if (IWL_FROM_FMM(right_master) > (short)iw_atten_tab[i]) {
			break;
		}
	}
	if (i)
		*right = i - 1;
	else
		*right = 0;
	if (*right < 0)
		*right = 0;
	if (*right > 127)
		*right = 127;
}

void iwl_codec_set_vol(short left, short right, int use)
{
	struct dig_voice_status *vs = &codec_playback_voice;
	short left_volume;
	short right_volume;

	if (use) {
		vs->insert_addr = (ULONG)MK_FP(iw_atten_tab[right], iw_atten_tab[left]);
	}
	left_volume = left_master + vs->volume + (short)(vs->insert_addr & 0x0000FFFF) + iwl_digital_master_volume + iwl_digital_mute_atten;
	right_volume = right_master + vs->volume + (short)(vs->insert_addr >> 16) + iwl_digital_master_volume + iwl_digital_mute_atten;
	if (left_volume > IW_MAX_VOLUME)
		left_volume = IW_MAX_VOLUME;
	if (right_volume > IW_MAX_VOLUME)
		right_volume = IW_MAX_VOLUME;

    OS_PUSH_DISABLE();
    IWL_CODEC_IN(LEFT_DAC_OUTPUT, iwl_cldaci);
    IWL_CODEC_IN(RIGHT_DAC_OUTPUT, iwl_crdaci);
	iwl_cldaci &= ~0x7F;
	iwl_crdaci &= ~0x7F;
	iwl_cldaci |= left_volume >> 6;
	iwl_crdaci |= right_volume >> 6;
	IWL_CODEC_OUT(LEFT_DAC_OUTPUT, iwl_cldaci);
	IWL_CODEC_OUT(RIGHT_DAC_OUTPUT, iwl_crdaci);
	OS_POP_FLAGS();
}

/***************************************************************************

FUNCTION DEFINITION:
iw_codec_dig_set_vol - set the volume for CODEC playback

DESCRIPTION:
This function sets the volume for digital audio playback via the
CODEC.  The volume setting is a linear scale ranging from 0 to 127.
The CODEC volume setting is also governed by the digital audio
master volume.

PARAMETERS:
	volume - new setting for CODEC volume 0 - 127

RETURNS: void
*/
void iw_codec_dig_set_vol(short volume)
{
	struct dig_voice_status *vs = &codec_playback_voice;

	if ((vs->status & STAT_MASK) != STAT_UNUSED) {
		if ((volume >= 0) && (volume <= 127)) vs->volume = (USHORT)IWL_TO_FMM(iw_atten_tab[volume]);
		if (vs->volume > IW_MAX_VOLUME) vs->volume = IW_MAX_VOLUME;
		iwl_codec_set_vol(0,0,0);
	}
}

/***************************************************************************

FUNCTION DEFINITION:
iw_codec_dig_set_pan - set the pan position for CODEC playback

DESCRIPTION:
This function sets the pan position for digital audio playback via the
CODEC.  The pan setting is a linear scale ranging from 0 to 127.
0 = full left and 127 = full right
The CODEC volume setting is also governed by the digital audio
master volume.

PARAMETERS:
	pan - new setting for CODEC volume 0 - 127

RETURNS: void
*/
void iw_codec_dig_set_pan(short pan)
{
	struct dig_voice_status *vs = &codec_playback_voice;
	short left, right;

	if ((vs->status & STAT_MASK) != STAT_UNUSED) {
	    left = iw_atten_tab[127 - pan] >> 1;
	    right = iw_atten_tab[pan] >> 1;
	    vs->insert_addr = (ULONG)MK_FP(right, left);
	    iwl_codec_set_vol(0, 0, 0);
	}
}

/***************************************************************************

FUNCTION DEFINITION:
dig_play_next_buffer_codec - fill DMA buffer with data from the wrapper

DESCRIPTION:
dig_play_next_buffer_codec has the role of taking digital audio data 
from the wrapper and transferring it to the DMA buffer.  This routine's
job is to fill the half of the DMA buffer that is not currently being
played.  

The data from the PC is transferred to the DMA buffer until the data
buffer is empty.  If the DMA buffer half fills up before the data buffer
is emptied, the data buffer is retained to fill up the next half of the
DMA buffer after it is through being played.  If the DMA buffer half is
not full after the PC data buffer is emptied, this routine requests more
data via a IW_DIG_MORE_DATA callback.  

The wrapper may respond three ways to this request:

1. IW_DIG_MORE_DATA - the wrapper has sent us more data and we continue
                      filling the DMA buffer half

2. IW_DIG_PAUSE - the wrapper has not sent more data and we are to
                  pause playback. (This type of pause may only be 
		  restarted with a call to iw_dig_play_next_buffer).

3. IW_DIG_DONE - the wrapper has no more data to sent and we should start
                 proceedures to shutdown digital audio playback.  (The
		 shutdown proceedure can be stopped by a call to 
		 iw_dig_play_next_buffer if the wrapper has more data to
		 play and has not received a IW_DIG_DONE callback.


Once a data buffer is empty, the empty buffer count is set to inform the
caller when the currently playing half of the DMA buffer is finished
playing.

RETURNS: void

SEE ALSO:
iwl_codec_playback
playback_codec_handler
*/
void dig_play_next_buffer_codec(int stop)
{
	struct dig_voice_status
	*vs;
	int rc;

#ifdef _WINDOWS
	void RFAR *newpc;
    char huge *data;
#endif


	UCHAR silence = 0x00;
	USHORT next_buf;

	vs = &codec_playback_voice;

	if (!last_buffer) {
		if (!user_pause) {
			if (vs->type & IW_TYPE_8BIT)
				silence = 0x80;
			if (vs->insert_len == 0) {
				vs->insert_len = vs->max;
				dma = (UCHAR RFAR *)vs->buffs[vs->insert_buf].addr_s;
				iwu_memset((void RFAR *)dma, (int)silence, (size_t)vs->insert_len);
			}
			while (vs->size > 0 && stop > 0 && vs->insert_len > 0 && !last_buffer && !user_pause) {
				transfer_size = min(vs->size, vs->insert_len);
#ifdef _WINDOWS
				newpc = MemCopySrc((void RFAR *)dma,
						 (void RFAR *)vs->pc_buffer,
						 (int) transfer_size);
#else
				iwu_memcpy((void RFAR *)dma,
					   (void RFAR *)vs->pc_buffer,
					   (size_t)transfer_size);
#endif
				vs->buffs[vs->insert_buf].status |= BUFSTAT_FILLED;
				vs->buffs[vs->insert_buf].play_size += transfer_size;
				vs->size -= transfer_size;
				vs->insert_len -= transfer_size;
				if (vs->size > 0) {
#ifdef _WINDOWS
					data = (char huge *)(vs->pc_buffer);
					data += transfer_size;
					vs->pc_buffer = (UCHAR RFAR *)data;
#else
					vs->pc_buffer = &vs->pc_buffer[transfer_size];
#endif
				}
				else {
					vs->buffs[vs->insert_buf].status |= BUFSTAT_DONE;
					vs->buffs[vs->insert_buf].dummy++;
				}
				if (vs->insert_len > 0) {
#ifdef _WINDOWS
					data = (char huge *)dma;
					data += transfer_size;
					dma = (UCHAR RFAR *)data;
#else
					dma = &dma[transfer_size];
#endif
				}
				else {
					vs->insert_buf++;
					if (vs->insert_buf == DMA_SPLIT) vs->insert_buf = 0;
					if (vs->buffs[vs->insert_buf].status == 0) {
						stop--;
					    vs->insert_len = vs->max;
					    dma = (UCHAR RFAR *)vs->buffs[vs->insert_buf].addr_s;
					    iwu_memset((void RFAR *)dma, (int)silence, (size_t)vs->insert_len);
					}
				}
				if (vs->size == 0) {
					if (vs->callback) {
						OS_PUSH_DS();
						rc = (*vs->callback)(IW_DIG_MORE_DATA, IW_CODEC_PLAYBACK_VOICE,
							   &vs->pc_buffer, &vs->size);
						OS_POP_DS();
					}
					else
						rc = IW_DIG_DONE;
					switch (rc) {
						case IW_DIG_MORE_DATA:
							rc = IW_DIG_MORE_DATA;
							break;
						case IW_DIG_DONE:
							last_buffer = 1;
							break;
						case IW_DIG_PAUSE:
							user_pause = 1;
							break;
						default:
							rc = IW_DIG_BUFFER_DONE;
							break;
					}
				}
			}
		}
		else
			iw_codec_pause_digital(IW_CODEC_PLAYBACK_VOICE);
	}
}

/***************************************************************************

FUNCTION DEFINITION:
playback_codec_handler - interrupt handler for CODEC playback

DESCRIPTION:
playback_codec_handler handles the interrupts generated by the CODEC when
the playcount ticks down to zero.  Following the general flow of playback,
this routine switches the playback buffer indicator to the next half
of the DMA buffer which should already have data in it.  If, for any
reason, the next half of the DMA buffer does not have data, playback
is stopped.

This routine also informs the wrapper of any data buffers that have been 
played through means of IW_DIG_BUFFER_DONE callbacks.

After these tasks have been accomplished, we transfer control to
dig_play_next_buffer_codec to refil the half of the DMA buffer that just
finished playing.

RETURNS: void

SEE ALSO:
dig_play_next_buffer_codec
iwl_codec_playback
*/
static void playback_codec_handler(void)
{
	struct dig_voice_status
	*vs = &codec_playback_voice;

	if (vs->callback && (vs->buffs[vs->play_buf].status & BUFSTAT_DONE)) {
		for (; vs->buffs[vs->play_buf].dummy > 0; vs->buffs[vs->play_buf].dummy--) {
			OS_PUSH_DS();
			(*vs->callback)(IW_DIG_BUFFER_DONE, IW_CODEC_PLAYBACK_VOICE, 0,0);
			OS_POP_DS();
		}
	}
	vs->position += vs->buffs[vs->play_buf].play_size;
	vs->buffs[vs->play_buf].status = 0;
	vs->buffs[vs->play_buf].play_size = 0;
	//vs->insert_buf = vs->play_buf;
	in_handler++;
	vs->play_buf++;
	if (vs->play_buf == DMA_SPLIT) vs->play_buf = 0;
	if (vs->buffs[vs->play_buf].status == 0)
		iw_codec_stop_digital(IW_CODEC_PLAYBACK_VOICE);
	else
		dig_play_next_buffer_codec(limit);
}

/***************************************************************************

FUNCTION DEFINITION:
iw_codec_play_digital - initiate digital audio playback through the CODEC

DESCRIPTION:
This routine performs the setup of the CODEC for playback of digital
audio data.  This routine will set up the data format and playback
rate requested by the caller.  If the caller has not requested
IW_TYPE_PRELOAD, the playback is started otherwise this routine
exists and we wait for an iw_codec_start_digital call.

PARAMETERS:
	buffer - pointer to a buffer to receive the data sampled
	size   - size of the above buffer in bytes
	volume - at which to playback the audio data.  This should
	         be between 0 and 127 inclusive.
	frequency - rate at which to sample the data
	type      - how to sample the data, this is a bitwise OR of one
				or more of the following:
				IW_TYPE_8BIT
				IW_TYPE_PRELOAD
				IW_TYPE_INVERT_MSB
				IW_TYPE_STEREO
				IW_TYPE_ALAW
				IW_TYPE_ULAW
				IW_TYPE_ADPCM
	st_work_buff - buffer to be used for DMA transfer from the
				   CODEC A/D converters
	callback - address of a function provided by the wrapper to
				receive notofications from the kernel during the
				sampling process

NOTE: This routine MUST be the first routine called when an application is
      going to perform digital playback.

RETURNS: int - voice number used for CODEC playback
               IW_NO_MORE_VOICES if the DMA channel for the CODEC is busy
*/
int iw_codec_play_digital(
	UCHAR RFAR *buffer,
	ULONG size,
	USHORT volume,
	USHORT pan,
	USHORT frequency,
	UCHAR type,
	struct iw_dma_buff RFAR *st_work_buff,
	int (RFAR *callback)(int, int, UCHAR RFAR * RFAR *, ULONG RFAR *))
{
	struct dig_voice_status
	*vs;
	int i,rc;
	unsigned long resolution;
	unsigned char shift;

	OS_PUSH_DISABLE();
	vs = &codec_playback_voice;
	if (iw_dma_ready(IW_DMA_CHAN_2) &&
		vs->status == STAT_UNUSED) {
		vs->pc_buffer = buffer;
		vs->size = size;
		vs->rate = frequency;
		vs->type = type;
		vs->pc_stbuff = st_work_buff;
		vs->callback = callback;
		vs->insert_buf = 0;
		vs->play_buf = 0;

		shift = 16;
		vs->max = frequency;
		if (type & IW_TYPE_STEREO) vs->max <<= 1;
		if (!(type & IW_TYPE_8BIT)) vs->max <<= 1;
		vs->max = (vs->max / (unsigned long)shift) & (unsigned long)0xFFFC;
		/* Woodruff Heuristic */
		if (vs->max > 1024 && vs->max < 2048) vs->max = 1024;
		//DMA_SPLIT = (unsigned int)max(min((vs->pc_stbuff->size/vs->max), MAX_BUFFS), 2);
		DMA_SPLIT = 2;
		if ((vs->max * DMA_SPLIT) > vs->pc_stbuff->size) {
		    vs->max = vs->pc_stbuff->size / DMA_SPLIT;
		}
		limit = MAX_BUFFS;

		vs->b_size = vs->max * DMA_SPLIT;

		vs->status = STAT_PAUSED;
		vs->insert_len = 0;
		last_buffer = 0;
		user_pause = 0;
		transfer_size = 0;

    /* Set the CODEC playback volume */
		iw_codec_dig_set_vol(volume);
		iw_codec_dig_set_pan(pan);

   /* Set the codec playback format */
		iwl_codec_data_format(vs, PLAYBACK_FORMAT);

   /* Set the codec interrupt service routine */
		iwl_codec_playback_func = playback_codec_handler;

   /* Start the codec playback */
		vs->insert_buf = 0;
		for (i=0; i < (int)DMA_SPLIT; i++) {
			vs->buffs[i].play_size = 0;
			vs->buffs[i].addr_s = (ULONG)(&vs->pc_stbuff->vptr[i * vs->max]);
			vs->buffs[i].status = 0;
			vs->buffs[i].dummy = 0;
		}
		if (vs->size > 0)
			dig_play_next_buffer_codec(limit*2);

    /* Program the DMA controller */
		iwl_codec_setup_play_dma(IW_DMA_CHAN_2, vs);
		iwl_codec_program_play_count((unsigned int)(vs->max));

		if (!(vs->type & IW_TYPE_PRELOAD))
			iw_codec_start_digital(IW_CODEC_PLAYBACK_VOICE);
		rc = IW_CODEC_PLAYBACK_VOICE;
		
	}
	else
		rc = IW_NO_MORE_VOICES;
	OS_POP_FLAGS();
	return(rc);
}
