//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/snd_mix.c                                              $
// $Revision:: 4                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 2/5/02 7:38 PM                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// portable code to mix sounds for snd_dma.c
//

#include "client.h"
#include "snd_loc.h"

#ifdef __USEA3D
#include "sin_a3d.h"
#include "a3dwrapperDP.h"
#include "q_shared.h"

int hA3D_FeedBuffer( void *pSrc, int iChannel, channel_t *ch, sfxcache_t *sc, float fPitch );

// Defined in cl_ents.c
void CL_GetEntityDimensions( int ent, vec3_t mins, vec3_t maxs /*, vec3_t clipmins, vec3_t clipmaxs, int *isclip*/ );
int PaintToA3D( int iChannel, channel_t *ch, sfxcache_t *sc );
void A3D_FinishA3DChannel( int i );
#endif // __USEA3D

#define PAINTBUFFER_SIZE    2048

portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE];

#ifdef _ADD_EAX_
#include "eax.h"
portable_samplepair_t rearpaintbuffer[PAINTBUFFER_SIZE];
#endif

int *snd_p, snd_linear_count, snd_vol;
short *snd_out;
int snd_musicvol, snd_dialogvol;

void S_WriteLinearBlastStereo16( void );

#if   !id386

void S_WriteLinearBlastStereo16( void )
{
	int i;
	int val;

	for (i=0; i<snd_linear_count; i+=2)
	{
		val = snd_p[i]>>8;
		if (val > 0x7fff)
			snd_out[i] = 0x7fff;
		else if (val < (short)0x8000)
			snd_out[i] = (short)0x8000;
		else
			snd_out[i] = val;

		val = snd_p[i+1]>>8;
		if (val > 0x7fff)
			snd_out[i+1] = 0x7fff;
		else if (val < (short)0x8000)
			snd_out[i+1] = (short)0x8000;
		else
			snd_out[i+1] = val;
	}
}
#endif

#if (id386) && defined(_MSC_VER)
__declspec( naked ) void S_WriteLinearBlastStereo16( void )
{
	__asm {

		push edi
		push ebx
		mov ecx,ds:dword ptr[snd_linear_count]
		mov ebx,ds:dword ptr[snd_p]
		mov edi,ds:dword ptr[snd_out]
LWLBLoopTop:
		mov eax,ds:dword ptr[-8+ebx+ecx*4]
		sar eax,8
		cmp eax,07FFFh
		jg LClampHigh
		cmp eax,0FFFF8000h
		jnl LClampDone
		mov eax,0FFFF8000h
		jmp LClampDone
LClampHigh:
		mov eax,07FFFh
LClampDone:
		mov edx,ds:dword ptr[-4+ebx+ecx*4]
		sar edx,8
		cmp edx,07FFFh
		jg LClampHigh2
		cmp edx,0FFFF8000h
		jnl LClampDone2
		mov edx,0FFFF8000h
		jmp LClampDone2
LClampHigh2:
		mov edx,07FFFh
LClampDone2:
		shl edx,16
		and eax,0FFFFh
		or edx,eax
		mov ds:dword ptr[-4+edi+ecx*2],edx
		sub ecx,2
		jnz LWLBLoopTop
		pop ebx
		pop edi
		    ret
	}
}

#endif

static void S_TransferStereo16( unsigned long *pbuf, int endtime )
{
	int lpos;
	int lpaintedtime;

	snd_p = (int *) paintbuffer;
	lpaintedtime = paintedtime;

	while (lpaintedtime < endtime)
	{
		// handle recirculating buffer issues
		lpos = lpaintedtime & ((dma.samples>>1)-1);

		snd_out = (short *) pbuf + (lpos<<1);

		snd_linear_count = (dma.samples>>1) - lpos;
		if (lpaintedtime + snd_linear_count > endtime)
			snd_linear_count = endtime - lpaintedtime;

		snd_linear_count <<= 1;

		// write a linear blast of samples
		S_WriteLinearBlastStereo16 ();

		snd_p += snd_linear_count;
		lpaintedtime += (snd_linear_count>>1);
	}
}

/*
===================
S_TransferPaintBuffer
===================
*/
static void S_TransferPaintBuffer( int endtime )
{
	int out_idx;
	int count;
	int out_mask;
	int     *p;
	int step;
	int val;
	unsigned long *pbuf;

	pbuf = (unsigned long *)dma.buffer;

	if (s_testsound->value)
	{
		int i;
		int count;

		// write a fixed sine wave
		count = (endtime - paintedtime);
		for (i=0; i<count; i++)
			paintbuffer[i].left = paintbuffer[i].right = sin((paintedtime+i)*0.1 )*20000*256;
	}


#ifdef _ADD_EAX_
	if ( EAX_format_set )
	{
		SEAX_TransferStereo16( paintedtime, endtime );
		return;
	}
#endif

	if (dma.samplebits == 16 && dma.channels == 2)
	{   // optimized case
		S_TransferStereo16 ( pbuf, endtime );
	}
	else
	{   // general case
		p = (int *) paintbuffer;
		count = (endtime - paintedtime) * dma.channels;
		out_mask = dma.samples - 1;
		out_idx = paintedtime * dma.channels & out_mask;
		step = 3 - dma.channels;

		if (dma.samplebits == 16)
		{
			short *out = (short *) pbuf;
			while (count--)
			{
				val = *p >> 8;
				p+= step;
				if (val > 0x7fff)
					val = 0x7fff;
				else if (val < (short)0x8000)
					val = (short)0x8000;
				out[out_idx] = val;
				out_idx = (out_idx + 1) & out_mask;
			}
		}
		else if (dma.samplebits == 8)
		{
			unsigned char *out = (unsigned char *) pbuf;
			while (count--)
			{
				val = *p >> 8;
				p+= step;
				if (val > 0x7fff)
					val = 0x7fff;
				else if (val < (short)0x8000)
					val = (short)0x8000;
				out[out_idx] = (val>>8) + 128;
				out_idx = (out_idx + 1) & out_mask;
			}
		}
	}
}


/*
===============================================================================

CHANNEL MIXING

===============================================================================
*/

void S_PaintChannelFrom8( channel_t *ch, sfxcache_t *sc, int endtime, int offset, int leftvol, int rightvol );
void S_PaintChannelFrom16( channel_t *ch, sfxcache_t *sc, int endtime, int offset, int leftvol, int rightvol );
void S_PaintChannelFromIMA_Mono( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol );
void S_PaintChannelFromIMA_Stereo( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol );
void S_PaintChannelFrom8_Stereo( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol );
void S_PaintChannelFrom16_Stereo( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol );

void S_PaintChannels( int endtime )
{
	int i;
	int end;
	channel_t *ch;
	sfxcache_t  *sc;
	int ltime, count;
	playsound_t *ps;

	snd_vol = s_volume->value*255;
	if ( snd_vol > 255 )
		snd_vol = 255;
	if ( snd_vol < 0 )
		snd_vol = 0;

//Com_Printf ("%i to %i\n", paintedtime, endtime);
	while (paintedtime < endtime)
	{
		// if paintbuffer is smaller than DMA buffer
		end = endtime;
		if (endtime - paintedtime > PAINTBUFFER_SIZE)
			end = paintedtime + PAINTBUFFER_SIZE;

		// start any playsounds
		while (1)
		{
			ps = s_pendingplays.next;
			if (ps == &s_pendingplays)
				break;  // no more pending sounds
			if (ps->begin <= paintedtime)
			{
				S_IssuePlaysound ( ps );
				continue;
			}

			if (ps->begin < end)
				end = ps->begin;        // stop here
			break;
		}

		// clear the paint buffer
		if (s_rawend < paintedtime)
		{
//			Com_Printf ("clear\n");
			memset( paintbuffer, 0, (end - paintedtime) * sizeof(portable_samplepair_t));
#ifdef _ADD_EAX_
			if ( EAX_format_set )
				memset( rearpaintbuffer, 0, (end - paintedtime) * sizeof(portable_samplepair_t));
#endif
		}
		else
		{   // copy from the streaming sound source
			int s;
			int stop;

			stop = (end < s_rawend) ? end : s_rawend;

			for (i=paintedtime; i<stop; i++)
			{
				s = i&(MAX_RAW_SAMPLES-1);
				paintbuffer[i-paintedtime] = s_rawsamples[s];
			}
//			if (i != end)
//				Com_Printf ("partial stream\n");
//			else
//				Com_Printf ("full stream\n");
			for (; i < end; i++)
			{
				paintbuffer[i-paintedtime].left =
					paintbuffer[i-paintedtime].right = 0;
			}
#ifdef _ADD_EAX_
			/* Don't want any crap in the rear buffer for cinematic audio */
			if ( EAX_format_set )
				memset( rearpaintbuffer, 0, (end - paintedtime) * sizeof(portable_samplepair_t));
#endif
		}

		// paint in the channels.
		ch = channels;
		for (i=0; i<MAX_CHANNELS; i++, ch++)
		{
			int leftvol, rightvol;
#ifdef _ADD_EAX_
			int rearleft, rearright;
#endif // _ADD_EAX_
#ifdef __USEA3D
			int A3D_good = false;

			if (snd_isa3d)
			{
				// Indicates that this was an autosound, but didn't get regenerated.
				if (ch->autosound && ch->pos == -1)
				{
					memset ( ch, 0, sizeof(*ch));
					hA3D_PauseBuffer ( i );
				}

				if (ch->sfx)
				{
					sc = S_LoadSound ( ch->sfx ); // get pointer into cached data
				}

				// If we can get this channel into 3D hardware, do it.
				if (PaintToA3D ( i, ch, sc ))
				{
					A3D_good = true;
				}
			}
#endif // __USEA3D
			if (ch->flags & SOUND_PAUSED)
				continue;

			ltime = paintedtime;

			while (ltime < end)
			{
#ifdef _ADD_EAX_
				if ( EAX_format_set )
				{
					if (!ch->sfx || (!ch->leftvol && !ch->rightvol && !ch->rleftvol && !ch->rrightvol) ) break;
				}
				else
#endif
				if (!ch->sfx || (!ch->leftvol && !ch->rightvol) )
					break;

				// max painting is to the end of the buffer
				count = end - ltime;

				// might be stopped by running out of data
				if (ch->end - ltime < count)
					count = ch->end - ltime;

				sc = S_LoadSound ( ch->sfx );
				if (!sc)
					break;

				//
				// adjust fading
				//
				if (ch->flags & SOUND_FADING) // we are currently fading
				{
					int fadevol;

					fadevol = ch->fadefrac>>SND_FRAC_BITS;
					if (fadevol < 0)
					{
						ch->flags &= ~SOUND_FADING;
						ch->fadestep = 0;
						ch->fadefrac = 0;
						if (ch->flags & SOUND_PAUSING)
						{
							ch->flags &= ~SOUND_PAUSING;
							ch->flags |= SOUND_PAUSED;
							// get out of this loop
							break;
						}
						else if (ch->entnum < 0)
						{
							MUSIC_SongFaded( ch );
						}
						else
						{
							ch->sfx = NULL;
						}
					}
					if (fadevol > 255)
					{
						ch->flags &= ~SOUND_FADING;
						fadevol = 255;
						ch->fadestep = 0;
						ch->fadefrac = 255<<SND_FRAC_BITS;
					}
					else
					{
						ch->fadefrac += ch->fadestep * (ltime - ch->fadepos);
						ch->fadepos = ltime; // set to current time
					}
					leftvol = (ch->leftvol*fadevol)>>8;
					rightvol = (ch->rightvol*fadevol)>>8;
#ifdef _ADD_EAX_
					if ( EAX_format_set )
					{
						rearleft = (ch->rleftvol*fadevol)>>8;
						rearright = (ch->rrightvol*fadevol)>>8;
					}
#endif
				}
				else
				{
					leftvol = ch->leftvol;
					rightvol = ch->rightvol;
#ifdef _ADD_EAX_
					if ( EAX_format_set )
					{
						rearleft = ch->rleftvol;
						rearright = ch->rrightvol;
					}
#endif
				}
				if ( ch->flags & SOUND_LOCAL_DIALOG )
				{
					leftvol *= snd_dialogvol;
					rightvol *= snd_dialogvol;
#ifdef _ADD_EAX_
					if ( EAX_format_set )
					{
						rearleft *= snd_dialogvol;
						rearright *= snd_dialogvol;
					}
#endif
				}
				else if ( ch->entnum < 0 )
				{
					leftvol *= snd_musicvol;
					rightvol *= snd_musicvol;
#ifdef _ADD_EAX_
					if ( EAX_format_set )
					{
						rearleft *= snd_musicvol;
						rearright *= snd_musicvol;
					}
#endif
				}
				else
				{
					leftvol *= snd_vol;
					rightvol *= snd_vol;
#ifdef _ADD_EAX_
					if ( EAX_format_set )
					{
						rearleft *= snd_vol;
						rearright *= snd_vol;
					}
#endif
				}

				if (count > 0 && ch->sfx)
				{
#ifdef __USEA3D
					// No good, gotta use 100% software...
					if (A3D_good)
					{
						ch->srcfrac += count * ch->srcstep;
						ch->pos += count;
					}
					else
					{
#endif // __USEA3D
					if (sc->width == 1)    // FIXME; 8 bit asm is wrong now
					{
						if (sc->stereo)
						{
							S_PaintChannelFrom8_Stereo( ch, sc, count,  ltime - paintedtime, leftvol, rightvol );
						}
						else
						{
#ifdef _ADD_EAX_
							if ( EAX_format_set )
								SEAX_PaintChannelFrom8( ch, sc, count,  ltime - paintedtime, leftvol, rightvol, rearleft, rearright );
							else
#endif
							S_PaintChannelFrom8( ch, sc, count,  ltime - paintedtime, leftvol, rightvol );
						}
					}
					else     // 16 or IMA
					{
						if (sc->type == WAV_PCM_FORMAT)
						{
							if (sc->stereo)
							{
								S_PaintChannelFrom16_Stereo( ch, sc, count,  ltime - paintedtime, leftvol, rightvol );
							}
							else
							{
#ifdef _ADD_EAX_
								if ( EAX_format_set )
									SEAX_PaintChannelFrom16( ch, sc, count,  ltime - paintedtime, leftvol, rightvol, rearleft, rearright );
								else
#endif
								S_PaintChannelFrom16( ch, sc, count,  ltime - paintedtime, leftvol, rightvol );
							}
						}
						else
						{
							if (!sc->stereo)
								S_PaintChannelFromIMA_Mono( ch, sc, count, ltime - paintedtime, leftvol, rightvol );
							else
								S_PaintChannelFromIMA_Stereo( ch, sc, count, ltime - paintedtime, leftvol, rightvol );
						}
					}

#ifdef __USEA3D
				}     // If A3D was able to play the sound, we skip all the Quake paint code.
#endif
					ltime += count;
				}
				// if at end of loop, restart
				if (ltime >= ch->end)
				{
					if ( ch->entnum < 0 )
					{
						if (MUSIC_SongEnded( ch ))
							continue;
					}
					if (ch->autosound)
					{   // autolooping sounds allways go back to start
						ch->pos = 0;
						ch->end = ltime + SAMPLE_TO_SIN_VALUE( sc->length );
						ch->srcfrac = SIN_TO_SAMPLE_VALUE( ch->pos );
						ch->srcfrac <<= SND_FRAC_BITS;
						// reset ima variables
						ch->state.real_pos = 0;
						ch->state.ima_pos = 0;
					}
					else if (sc->loopstart >= 0 || ch->entnum < 0) // music auto loops
					{
						ch->pos = SAMPLE_TO_SIN_VALUE( sc->loopstart );
						ch->end = ltime + SAMPLE_TO_SIN_VALUE( sc->length ) - ch->pos;
						ch->srcfrac = SIN_TO_SAMPLE_VALUE( ch->pos );
						ch->srcfrac <<= SND_FRAC_BITS;
						// reset ima variables
						ch->state.real_pos = 0;
						ch->state.ima_pos = 0;
					}
					else
					{   // channel just stopped
						ch->sfx = NULL;
					}
				}
			}
		}

#ifdef _ADD_EAX_
		if ( EAX_format_set )
			S_TransferPaintBuffer( end );
		else
#endif
		// transfer out according to DMA format
#ifdef __USEA3D
		if (dma.buffer)
#endif
		S_TransferPaintBuffer( end );

		paintedtime = end;
	}
}

void S_InitScaletable( void )
{
	// setup main volume
	s_volume->modified = false;
	snd_vol = s_volume->value*255;

	if ( snd_vol > 255 )
		snd_vol = 255;
	if ( snd_vol < 0 )
		snd_vol = 0;

	// setup dialog volume
	s_dialogvolume->modified = false;
	snd_dialogvol = s_dialogvolume->value * 255;

	if ( snd_dialogvol > 255 )
		snd_dialogvol = 255;
	if ( snd_dialogvol < 0 )
		snd_dialogvol = 0;

	// setup music volume
	s_musicvolume->modified = false;
	snd_musicvol = s_musicvolume->value * 255;

	if ( snd_musicvol > 255 )
		snd_musicvol = 255;
	if ( snd_musicvol < 0 )
		snd_musicvol = 0;
}


#if 1 /* ((!id386) || (defined(SIN))) */

void S_PaintChannelFrom8( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol )
{
	int data;
	int i;
	portable_samplepair_t   *samp;

	signed char *sfx;
	int left, right;

	sfx = (signed char *)sc->data;
	samp = &paintbuffer[offset];
	for (i=0; i<count; i++, samp++, ch->srcfrac+=ch->srcstep)
	{
		data = sfx[ch->srcfrac>>SND_FRAC_BITS];
		left = data * leftvol;
		right = data * rightvol;
		samp->left += left;
		samp->right += right;
	}
	ch->pos += count;
}
#endif

#if 0 /* (id386) && defined(_MSC_VER) && !defined(SIN) */

__declspec( naked ) void S_PaintChannelFrom8( channel_t *ch, sfxcache_t *sc, int count, int offset )
{
	__asm {
		push esi
		push edi
		push ebx
		push ebp
		mov ebx,ds:dword ptr[4+16+esp]
		mov esi,ds:dword ptr[8+16+esp]
		mov eax,ds:dword ptr[4+ebx]
		mov edx,ds:dword ptr[8+ebx]
		cmp eax,255
		jna LLeftSet
		mov eax,255
LLeftSet:
		cmp edx,255
		jna LRightSet
		mov edx,255
LRightSet:
		and eax,0F8h
		// this is here because of new type variable, new blockalign variable and new reallength variable
		add esi,32
		and edx,0F8h
		mov edi,ds:dword ptr[16+ebx]
		mov ecx,ds:dword ptr[12+16+esp]
		add esi,edi
		shl eax,7
		add edi,ecx
		shl edx,7
		mov ds:dword ptr[16+ebx],edi
		add eax,offset snd_scaletable
		add edx,offset snd_scaletable
		sub ebx,ebx
		mov bl,ds:byte ptr[-1+esi+ecx*1]
		test ecx,1
		jz LMix8Loop
		mov edi,ds:dword ptr[eax+ebx*4]
		mov ebp,ds:dword ptr[edx+ebx*4]
		add edi,ds:dword ptr[paintbuffer+0-8+ecx*8]
		add ebp,ds:dword ptr[paintbuffer+4-8+ecx*8]
		mov ds:dword ptr[paintbuffer+0-8+ecx*8],edi
		mov ds:dword ptr[paintbuffer+4-8+ecx*8],ebp
		mov bl,ds:byte ptr[-2+esi+ecx*1]
		dec ecx
		jz LDone
LMix8Loop:
		mov edi,ds:dword ptr[eax+ebx*4]
		mov ebp,ds:dword ptr[edx+ebx*4]
		add edi,ds:dword ptr[paintbuffer+0-8+ecx*8]
		add ebp,ds:dword ptr[paintbuffer+4-8+ecx*8]
		mov bl,ds:byte ptr[-2+esi+ecx*1]
		mov ds:dword ptr[paintbuffer+0-8+ecx*8],edi
		mov ds:dword ptr[paintbuffer+4-8+ecx*8],ebp
		mov edi,ds:dword ptr[eax+ebx*4]
		mov ebp,ds:dword ptr[edx+ebx*4]
		mov bl,ds:byte ptr[-3+esi+ecx*1]
		add edi,ds:dword ptr[paintbuffer+0-8*2+ecx*8]
		add ebp,ds:dword ptr[paintbuffer+4-8*2+ecx*8]
		mov ds:dword ptr[paintbuffer+0-8*2+ecx*8],edi
		mov ds:dword ptr[paintbuffer+4-8*2+ecx*8],ebp
		sub ecx,2
		jnz LMix8Loop
LDone:
		pop ebp
		pop ebx
		pop edi
		pop esi
		    ret
	}
}

#endif

void S_PaintChannelFrom16( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol )
{
	int data;
	int left, right;
	signed short *sfx;
	int i;
	portable_samplepair_t   *samp;

	sfx = (signed short *)sc->data;
	samp = &paintbuffer[offset];
	for (i=0; i<count; i++, samp++, ch->srcfrac+=ch->srcstep )
	{
		data = sfx[ch->srcfrac>>SND_FRAC_BITS];
		left = (data * leftvol)>>8;
		right = (data * rightvol)>>8;
		samp->left += left;
		samp->right += right;
	}

	ch->pos += count;
}

void S_PaintChannelFrom8_Stereo( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol )
{
	int left, right;
	unsigned short *sfx;
	signed char *srcptr;
	int i;
	portable_samplepair_t   *samp;

	sfx = (unsigned short *)sc->data;

	samp = paintbuffer + offset;

	for (i=0; i<count; i++, samp++, ch->srcfrac+=ch->srcstep)
	{
		srcptr = ( signed char * )&sfx[ch->srcfrac>>SND_FRAC_BITS];
		left = ((*srcptr++) * leftvol);
		right = ((*srcptr) * rightvol);
		samp->left += left;
		samp->right += right;
	}
	ch->pos += count;
}

void S_PaintChannelFrom16_Stereo( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol )
{
	int data;
	int left, right;
	unsigned *sfx;
	int i;
	portable_samplepair_t   *samp;

	sfx = (unsigned *)sc->data;

	samp = paintbuffer + offset;
	for (i=0; i<count; i++, samp++, ch->srcfrac+=ch->srcstep)
	{
		data = sfx[ch->srcfrac>>SND_FRAC_BITS];
		left = ((signed short)(data&0xffff) * leftvol)>>8;
		right = ((signed short)(data>>16) * rightvol)>>8;
		samp->left += left;
		samp->right += right;
	}

	ch->pos += count;
}

//
// IMA ADPCM DECODE STUFF
//
static const int stepSizeTable[89] = {
	7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34,
	37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143,
	157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494,
	544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552,
	1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026,
	4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442,
	11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623,
	27086, 29794, 32767
};

static const int indexAdjustTable[16] = {
	-1, -1, -1, -1,  // +0 - +3, decrease the step size
	2, 4, 6, 8,     // +4 - +7, increase the step size
	-1, -1, -1, -1,  // -0 - -3, decrease the step size
	2, 4, 6, 8,     // -4 - -7, increase the step size
};

static __inline int imaadpcmSampleDecode( int encoded, int predicted, int stepsize )
{
	int lDifference;
	int lNewSample;

	//
	//  calculate difference:
	//
	//      lDifference = (encoded + 1/2) * stepsize / 4
	//
	lDifference = stepsize>>3;

	if (encoded & 4)
		lDifference += stepsize;

	if (encoded & 2)
		lDifference += stepsize>>1;

	if (encoded & 1)
		lDifference += stepsize>>2;

	//
	//  If the 'sign bit' of the encoded nibble is set, then the
	//  difference is negative...
	//
	if (encoded & 8)
		lDifference = -lDifference;

	//
	//  adjust predicted sample based on calculated difference
	//
	lNewSample = predicted + lDifference;

	//
	//  check for overflow and clamp if necessary to a 16 signed sample.
	//  Note that this is optimized for the most common case, when we
	//  don't have to clamp.
	//
	if( (long)(short)lNewSample == lNewSample )
	{
		return (int)lNewSample;
	}

	//
	//  Clamp.
	//
	if( lNewSample < -32768 )
		return (int)-32768;
	else
		return (int)32767;
}

static __inline int imaadpcmNextStepIndex( int nEncodedSample, int nStepIndex )
{
	//
	//  compute new stepsize step
	//
	nStepIndex += indexAdjustTable[nEncodedSample];

	if (nStepIndex < 0)
		nStepIndex = 0;
	else if (nStepIndex > 88)
		nStepIndex = 88;

	return (nStepIndex);
}

void S_PaintChannelFromIMA_Mono( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol )
{
	int left, right;
	unsigned char *sfx;
	portable_samplepair_t   *samp;
	ImaState * state;

	state = &ch->state;
	// check for wackiness
	// we don't really care about a single slipped sample though
	if (abs( state->ima_pos-(ch->srcfrac>>SND_FRAC_BITS) ) > 10)
	{
		if (ch->autosound)
		{
			int pos;
			int numblocks;

			pos = ch->srcfrac>>SND_FRAC_BITS;
			state->real_pos = pos>>1;
			numblocks = ( state->real_pos / sc->blockalign) + 1;
			state->real_pos += numblocks*3.5;
			state->real_pos = state->real_pos & ~(sc->blockalign-1);
			numblocks = ( (state->real_pos-1) / sc->blockalign) + 1;
			state->ima_pos = (state->real_pos<<1) - (numblocks*7) - 2;
		}
		else
		{
			Com_DPrintf (DEVELOPER_MSG_SOUND, "IMA wacky value, resetting.\n");
			state->real_pos = 0;
			state->ima_pos = 0;
		}
	}
	// make sure we are in bounds
	if (state->index < 0)
		state->index = 0;
	else if (state->index > 88)
		state->index = 88;

	sfx = (unsigned char *)sc->data + state->real_pos;

	ch->pos += count;
	samp = &paintbuffer[offset];
	while( count-- )
	{
		int pos;
		pos = ch->srcfrac>>SND_FRAC_BITS;
		while (state->ima_pos <= pos)
		{
			// catch up
			int deltaCode;

			// see if we are on a block boundary
			if ( !(state->real_pos & (sc->blockalign-1)) )
			{
				state->previousValue = (short) (sfx[0] | (sfx[1] << 8));
				state->index  = (int) sfx[2];
				state->real_pos += 4;
				sfx   += 4;
			}
			else
			{
				if (state->nibble)
				{
					deltaCode = (*sfx)>>4;
					sfx++;
					state->real_pos++;
					if ( state->real_pos >= sc->real_length )
					{
						state->real_pos = 0;
						state->ima_pos = 0;
						return;
					}
				}
				else
				{
					deltaCode = (*sfx) & 0xf;
				}
				state->nibble ^= 1;
				// Construct the difference by scaling the current step size
				// This is approximately: difference = (deltaCode+.5)*step/4
				state->previousValue = imaadpcmSampleDecode( deltaCode, state->previousValue, stepSizeTable[state->index] );
				// Update the step for the next sample
				state->index = imaadpcmNextStepIndex( deltaCode, state->index );
			}

			state->ima_pos++;
		}
		left = (state->previousValue * leftvol)>>8;
		right = (state->previousValue * rightvol)>>8;
		samp->left += left;
		samp->right += right;

		ch->srcfrac += ch->srcstep;
		samp++;
	}
}

void S_PaintChannelFromIMA_Stereo( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol )
{
	int left, right;
	unsigned char *sfx;
	portable_samplepair_t   *samp;
	ImaState * state;

	state = &ch->state;
	// check for wackiness
	// we don't really care about a single slipped sample though
	if (abs( state->ima_pos-(ch->srcfrac>>SND_FRAC_BITS) ) > 10)
	{
		int pos;

		pos = (ch->srcfrac>>SND_FRAC_BITS);
		pos /= sc->blockalign;
		state->ima_pos = pos * sc->blockalign;
		state->real_pos = (pos * sc->blockalign ) + ( (pos-1) * sizeof( unsigned ) * 2 );
		Com_DPrintf (DEVELOPER_MSG_SOUND, "IMA wacky value, resetting.\n");
		// state->real_pos = 0;
		// state->ima_pos = 0;
	}
	// left channel
	// make sure we are in bounds
	if (state->index < 0)
		state->index = 0;
	else if (state->index > 88)
		state->index = 88;

	// right channel
	// make sure we are in bounds
	if (state->r_index < 0)
		state->r_index = 0;
	else if (state->r_index > 88)
		state->r_index = 88;

	sfx = (unsigned char *)sc->data + state->real_pos;

	ch->pos += count;
	samp = paintbuffer + offset;
	while( count-- )
	{
		int pos;
		int leftcode;
		int rightcode;

		pos = ch->srcfrac>>SND_FRAC_BITS;
		while (state->ima_pos <= pos)
		{
			// catch up
			// see if we are on a block boundary
			if ( !(state->real_pos & (sc->blockalign-1)) )
			{
				state->previousValue = (short) (sfx[0] | (sfx[1] << 8));
				state->index  = (int) sfx[2];
				state->real_pos += 4;
				sfx   += 4;

				state->r_previousValue = (short) (sfx[0] | (sfx[1] << 8));
				state->r_index  = (int) sfx[2];
				state->real_pos += 4;
				sfx   += 4;
			}
			else
			{
				leftcode = (*sfx) & 0xf;
				rightcode = (*sfx)>>4;
				sfx++;
				state->real_pos++;

				// left channel
				state->previousValue = imaadpcmSampleDecode( leftcode, state->previousValue, stepSizeTable[state->index] );
				state->index = imaadpcmNextStepIndex( leftcode, state->index );

				// rightt channel
				state->r_previousValue = imaadpcmSampleDecode( rightcode, state->r_previousValue, stepSizeTable[state->r_index] );
				state->r_index = imaadpcmNextStepIndex( rightcode, state->r_index );
			}
			state->ima_pos++;
		}
		left = (state->previousValue * leftvol)>>8;
		right = (state->r_previousValue * rightvol)>>8;
		samp->left += left;
		samp->right += right;

		ch->srcfrac += ch->srcstep;
		samp++;
	}
}

#ifdef __USEA3D


int PaintToA3D( int iChannel, channel_t *ch, sfxcache_t *sc )
{
	void *pA3D = hA3D_Get3DSource ( iChannel, false );
	int bytesWritten;
	float origin[3];

	// This call fails if we have no 3d hardware sources for this channel.
	if (!pA3D) {
		return false;
	}

	// DEBUGGING
	debugSetName ( ch->sfx->name );

	if (ch->sfx && strstr ( ch->sfx->name, "stmmchn" ) != 0)
		ch = ch;

	// Transfer the data to the buffer, converting to 16-bit if necessary.
	bytesWritten = hA3D_FeedBuffer ( pA3D, iChannel, ch, sc, ch->pitch );

	if (!bytesWritten) {
		return false;
	}

	// If the engine doesn't care about it anymore, neither does A3D.
	if (!ch->sfx)
		return true;


	/////////////////
	// 3-D POSITION
	/////////////////
	if (ch->entnum == cl.playernum+1)
	{
		// Move all player sounds to the min distance in front of the player.
		// Since player sounds are not occluded, this is safe.
		float fMult = 0.3 * gpclDistance->value;

		origin[0] = listener_origin[0] + fMult * listener_forward[0];
		origin[1] = listener_origin[1] + fMult * listener_forward[1];
		origin[2] = listener_origin[2] + fMult * listener_forward[2];
	}
	else
	if (ch->fixed_origin || ch->autosound)
		VectorCopy ( ch->origin, origin );
	else {
		CL_GetEntitySoundOrigin ( ch->entnum, origin );

		// BIG HACK - the server positions sources at the feet of the models,
		// so they are occluded by slightly higher then floor level polys.
		// This has the effect of causing enemies to be occluded when they're
		// standing behind small obstacles, like benches.
		origin[2] += 3 * gpclDistance->value;
	}

	A3D_SetSourcePosition ( pA3D, origin );

	///////////
	// VOLUME
	///////////
	if (ch->flags & SOUND_FADING) // we are currently fading
	{
		int fadevol;

		fadevol = ch->fadefrac>>SND_FRAC_BITS;
		if (fadevol < 0)
		{
			fadevol = 0;
			if (ch->flags & SOUND_PAUSING)
				hA3D_PauseBuffer( iChannel );

			A3D_SetSourceVolume ( pA3D, 0.0f );
		}
		else
			A3D_SetSourceVolume ( pA3D, (float)(ch->master_vol * fadevol)/ (2.0 * 255.0f));
	}
	else
	{
		float fVolume;
		fVolume = (float) (ch->master_vol / 255.0);

		// Bounds checking
		fVolume = max ( 0.0f, fVolume );
		fVolume = min ( 1.0f, fVolume );

		A3D_SetSourceVolume ( pA3D, fVolume );
	}


	///////////////////
	// DISTANCE MODEL
	///////////////////
	{
		float attenuation;

		if (ch->entnum == cl.playernum+1)
			attenuation = 0;
		else
			attenuation = ch->dist_mult / 0.0005;

		hA3D_SetDistMult ( iChannel, attenuation );
	}


	//////////
	// PITCH
	//////////
	// Set the pitch, adjusting for the different speeds of each source.
	if (ch->pitch >= 0)
	{
		float fPitch = ch->pitch;

		fPitch *= (float)sc->speed / dma.speed; // Scale pitch for different sample rates.
		A3D_SetSourcePitch ( pA3D, fPitch );
	}

	/*
	   ////////////
	   // DOPPLER
	   ////////////
	   // Currently, doppler is broken.  This code would turn on velocity.
	   if (cl_entities)
	   {
	   float vel[3];
	      vel[0] = cl_entities[ch->entnum].baseline.velocity[0];
	      vel[1] = cl_entities[ch->entnum].baseline.velocity[1];
	      vel[2] = cl_entities[ch->entnum].baseline.velocity[2];
	      A3D_SetSourceVelocity (pA3D, vel);
	   }
	 */

	// DEBUGGING
	debugSetName ( NULL );

	return true;
}

#endif // __USEA3D

#ifdef _ADD_EAX_
void SEAX_TransferStereo16( int lpaintedtime, int endtime )
{
	int lpos, i, val, *snd_rp;
	short  *snd_out_fleft, *snd_out_fright, *snd_out_rleft, *snd_out_rright;

	SNDEAX_LockBuffers();

	if ( g_pdwWriteFL && g_pdwWriteFR && g_pdwWriteRL && g_pdwWriteRR )
	{
		snd_p = (int *)paintbuffer;
		snd_rp = (int *)rearpaintbuffer;

		while (lpaintedtime < endtime)
		{
			lpos = lpaintedtime & ((dma.samples>>1)-1);

			snd_linear_count = (dma.samples>>1) - lpos;

			if (snd_linear_count > endtime - lpaintedtime)
				snd_linear_count = endtime - lpaintedtime;

			snd_out_fleft = (short *)g_pdwWriteFL + lpos;
			snd_out_fright = (short *)g_pdwWriteFR + lpos;
			snd_out_rleft = (short *)g_pdwWriteRL + lpos;
			snd_out_rright = (short *)g_pdwWriteRR + lpos;

			snd_linear_count <<= 1;

			for (i=0; i<snd_linear_count; i+=2)
			{
				val = (snd_p[i])>>8;
				if (val > 0x7fff)
					snd_out_fleft[i>>1] = 0x7fff;
				else if (val < (short)0x8000)
					snd_out_fleft[i>>1] = (short)0x8000;
				else
					snd_out_fleft[i>>1] = val;

				val = (snd_p[i + 1])>>8;
				if (val > 0x7fff)
					snd_out_fright[i>>1] = 0x7fff;
				else if (val < (short)0x8000)
					snd_out_fright[i>>1] = (short)0x8000;
				else
					snd_out_fright[i>>1] = val;

				val = (snd_rp[i])>>8;
				if (val > 0x7fff)
					snd_out_rleft[i>>1] = 0x7fff;
				else if (val < (short)0x8000)
					snd_out_rleft[i>>1] = (short)0x8000;
				else
					snd_out_rleft[i>>1] = val;

				val = (snd_rp[i + 1])>>8;
				if (val > 0x7fff)
					snd_out_rright[i>>1] = 0x7fff;
				else if (val < (short)0x8000)
					snd_out_rright[i>>1] = (short)0x8000;
				else
					snd_out_rright[i>>1] = val;
			}

			snd_p += snd_linear_count;
			snd_rp += snd_linear_count;
			lpaintedtime += (snd_linear_count>>1);
		}
	}

	SNDEAX_UnlockBuffers();
}

void SEAX_PaintChannelFrom16( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol, int rearleft, int rearright )
{
	int data;
	int left, right, rleft, rright;
	signed short *sfx;
	int i;
	portable_samplepair_t   *samp, *rearsamp;

	sfx = (signed short *)sc->data;
	samp = &paintbuffer[offset];
	rearsamp = &rearpaintbuffer[offset];

	for (i=0; i<count; i++, samp++, rearsamp++, ch->srcfrac+=ch->srcstep )
	{
		data = sfx[ch->srcfrac>>SND_FRAC_BITS];
		left = (data * leftvol)>>8;
		right = (data * rightvol)>>8;
		rleft = (data * rearleft)>>8;
		rright = (data * rearright)>>8;
		samp->left += left;
		samp->right += right;
		rearsamp->left += rleft;
		rearsamp->right += rright;
	}

	ch->pos += count;
}

void SEAX_PaintChannelFrom8( channel_t *ch, sfxcache_t *sc, int count, int offset, int leftvol, int rightvol, int rearleft, int rearright )
{
	int data;
	int i;
	portable_samplepair_t   *samp, *rearsamp;
	signed char *sfx;
	int left, right, rleft, rright;

	sfx = (signed char *)sc->data;
	samp = &paintbuffer[offset];
	rearsamp = &rearpaintbuffer[offset];

	for (i=0; i<count; i++, samp++, rearsamp++, ch->srcfrac+=ch->srcstep)
	{
		data = sfx[ch->srcfrac>>SND_FRAC_BITS];
		left = data * leftvol;
		right = data * rightvol;
		rleft = data * rearleft;
		rright = data * rearright;
		samp->left += left;
		samp->right += right;
		rearsamp->left += rleft;
		rearsamp->right += rright;
	}

	ch->pos += count;
}

#endif
