/*
Copyright (c) 1998 Richard Lawrence

This program is free software; you can redistribute it and/or modify it under the terms 
of the GNU General Public License as published by the Free Software Foundation; either 
version 2 of the License, or (at your option) any later version. This program is 
distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details. You should have received a copy of the GNU
General Public License along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include <stdio.h>
#include <windows.h>
#include <mmsystem.h>
#include <limits.h>
#include <crtdbg.h>
#include <dsound.h>
#include "winatari.h"
#include "pokeysnd.h"
#include "registry.h"
#include "atari.h"
#include "resource.h"

#define DEFAULT_SOUND_BUFFER_SIZE	4096
#define NUM_SOUND_BUFS 8
#define SOUND_LATENCY_IN_FRAMES 2
#define POKEY_UPDATE_DIVISOR_DEFAULT 2
#define POKEY_SLOP_BYTES 64

extern long nSleepThreshold;
extern void SetSafeDisplay( void );
extern HWND	hWnd, MainhWnd;
extern ULONG ulAtariState;
extern void WriteRegDWORD( HKEY hkInput, char *item, DWORD value);
extern ULONG	ulDeltaT;
extern ULONG	ulAtari_HW_Nexttime;
extern TCHAR	gcErrorString[];
BOOL	bTimerRollover = FALSE;

extern TVmode tv_mode;
extern LARGE_INTEGER	lnTimeFreq;

ULONG	ulSoundState = SOUND_MMSOUND;
int	nSoundRate = 44100, nSoundVol = 0;
static unsigned int	unSampleSize = 0;

static WAVEFORMATEX	WaveFormat;
static HWAVEOUT		hWaveOut = 0;
static WAVEHDR		WaveHDR[NUM_SOUND_BUFS];
static char *pcSndBuf = NULL;
static char *pcCurSoundBuf = NULL;
static unsigned int unSwitchSndBuf = 1, nFrameCount = 1;
static unsigned int unPlayBuf = 0, unNextSndTick = 0;
static DWORD	dwStartVolume=0;

static BOOL GetDSErrorString( HRESULT hResult, LPSTR lpszErrorBuff, DWORD cchError);
static BOOL GetMMErrorString( MMRESULT mmResult, LPSTR lpszErrorBuff, DWORD cchError);

static BOOL bSoundIsPaused = FALSE;
static void SndMngr_NoSound( void );
static void SndPlay_MMSound( void );
static void SndMngr_MMSound( void );

void Restart_Sound( void );
void	(*Atari_PlaySound)(void) = SndMngr_NoSound;

void Clear_Sound( BOOL bPermanent );
void Sound_SetVolume( void );

extern ULONG	ulAtari_HW_Starttime;
extern int		ulMiscStates;

FILE *sndoutput = NULL;

static unsigned int	unUpdateCount = 1;
int	nSkipUpdateDefault = POKEY_UPDATE_DIVISOR_DEFAULT;
static unsigned int unSamplePos;
static unsigned int unRealUpdatesPerSample = 262 / POKEY_UPDATE_DIVISOR_DEFAULT;
static unsigned int	unSkipPokeyUpdate = POKEY_UPDATE_DIVISOR_DEFAULT;

#ifdef USE_DSOUND
static LPDIRECTSOUND				lpDSound = NULL;
static LPDIRECTSOUNDBUFFER			lpDsb1 = NULL;
static LPDIRECTSOUNDBUFFER			lpDsb2 = NULL;
static LPDIRECTSOUNDBUFFER			lpDsbCur = NULL;
static LPDIRECTSOUNDBUFFER			lpDsPrimary = NULL;

static void SndPlay_DsoundCoop( void );
static void SndMngr_DsoundCoop( void );

static void SndPlay_DsoundPrimary( void );
static void SndMngr_DsoundPrimary( void );

#endif

static void wait_for_vbi( void )
{
	long	nSpareTicks;
	ULONG	ulTimerLastVal = ulAtari_HW_Nexttime;
#ifdef USE_PERF_COUNTER
	LARGE_INTEGER	lnTicks;

	QueryPerformanceCounter( &lnTicks );
	if( bTimerRollover )
	{
		while( lnTicks.LowPart > ulAtari_HW_Nexttime && (ulAtari_HW_Nexttime - lnTicks.LowPart < ulDeltaT) )
		{
			QueryPerformanceCounter( &lnTicks );
			nSpareTicks = ULONG_MAX - lnTicks.LowPart;
			_ASSERT( nSpareTicks <= (long)ulDeltaT );
			if( nSpareTicks > nSleepThreshold )
			{
				SleepEx( SLEEP_TIME_IN_MS, TRUE );
				QueryPerformanceCounter( &lnTicks );
			}
		}
		bTimerRollover = FALSE;
	}
	nSpareTicks = (long)(ulAtari_HW_Nexttime - lnTicks.LowPart);
	//nsparetime = nSpareTicks * 100 / ulDeltaT;
	if( nSpareTicks > 0 )
	{
		if( !(ulMiscStates & ATARI_FULL_SPEED ) )
		{
			while( nSpareTicks > nSleepThreshold )
			{
				SleepEx( SLEEP_TIME_IN_MS, TRUE );
				QueryPerformanceCounter( &lnTicks );
				nSpareTicks = (long)(ulAtari_HW_Nexttime - lnTicks.LowPart);
			}
			while( ulAtari_HW_Nexttime > lnTicks.LowPart && lnTicks.LowPart > ulTimerLastVal )
				QueryPerformanceCounter( &lnTicks );
			ulAtari_HW_Nexttime += ulDeltaT;
		}
		else
		{
			if( nSpareTicks > (long)ulDeltaT )
				ulAtari_HW_Nexttime = lnTicks.LowPart + ulDeltaT;
			else
				ulAtari_HW_Nexttime += ulDeltaT;
		}
	}
	else
	{
		if( -nSpareTicks > (long)ulDeltaT )
			ulAtari_HW_Nexttime = lnTicks.LowPart + ulDeltaT;
		else
			ulAtari_HW_Nexttime += ulDeltaT;
	}
#else
	nSpareTicks = (long)(ulAtari_HW_Nexttime - timeGetTime() );
	if( nSpareTicks > 0 )
	{
		if( !(ulMiscStates & ATARI_FULL_SPEED ) )
		{
			while( nSpareTicks > nSleepThreshold )
			{
				SleepEx( SLEEP_TIME_IN_MS, TRUE );
				nSpareTicks = (long)(ulAtari_HW_Nexttime - timeGetTime() );
			}
			while( ulAtari_HW_Nexttime > timeGetTime() );
		}
		ulAtari_HW_Nexttime += ulDeltaT;
	}
	else
	{
		if( -nSpareTicks > ulDeltaT )
			ulAtari_HW_Nexttime = timeGetTime() + ulDeltaT;
		else
			ulAtari_HW_Nexttime += ulDeltaT;
	}
#endif

	if( ulTimerLastVal > ulAtari_HW_Nexttime )
		bTimerRollover = TRUE;
};

static void ShowMMError( UINT nUID, MMRESULT mmResult, BOOL quit )
{
	TCHAR	error[LOADSTRING_STRING_SIZE];
	TCHAR	fullstring[LOADSTRING_STRING_SIZE];
	TCHAR	action[LOADSTRING_STRING_SIZE];
	
	SetSafeDisplay();
	
	Clear_Sound( TRUE );
	GetMMErrorString( mmResult, error, LOADSTRING_STRING_SIZE );
	LoadString( NULL, IDS_MMSOUND_ERROR_PROMPT, gcErrorString, LOADSTRING_STRING_SIZE );
	LoadString( NULL, nUID, action, LOADSTRING_STRING_SIZE );
	wsprintf( fullstring, gcErrorString, action, error );
	LoadString( NULL, IDS_MMSOUND_ERROR_HDR, gcErrorString, LOADSTRING_STRING_SIZE );
	MessageBox( hWnd, fullstring, gcErrorString, MB_ICONSTOP );
	
	ulSoundState = SOUND_NOSOUND;
	WriteRegDWORD( NULL, REG_SOUND_STATE, ulSoundState);
	
	ulAtariState = ATARI_UNINITIALIZED | ATARI_PAUSED;
	_ASSERT( 0 );
	if( quit )
		PostMessage( MainhWnd, WM_CLOSE, 0, 0L );
	return;
}

#ifdef USE_DSOUND
static void ShowDSoundError( UINT nUID, HRESULT hResult, BOOL quit )
{
	TCHAR	error[LOADSTRING_STRING_SIZE];
	TCHAR	fullstring[LOADSTRING_STRING_SIZE];
	TCHAR	action[LOADSTRING_STRING_SIZE];
	
	SetSafeDisplay();
	
	Clear_Sound( TRUE );
	GetDSErrorString( hResult, error, 256 );
	LoadString( NULL, IDS_DSOUND_ERROR_REPORT, gcErrorString, LOADSTRING_STRING_SIZE );
	LoadString( NULL, nUID, action, LOADSTRING_STRING_SIZE );
	wsprintf( fullstring, gcErrorString, action, error );
	LoadString( NULL, IDS_DSOUND_ERROR_HDR, gcErrorString, LOADSTRING_STRING_SIZE );
	MessageBox( hWnd, fullstring, gcErrorString, MB_ICONSTOP );
	
	ulSoundState |= SOUND_NOSOUND;
	WriteRegDWORD( NULL, REG_SOUND_STATE, ulSoundState);
	
	ulAtariState = ATARI_UNINITIALIZED | ATARI_PAUSED;
	_ASSERT( 0 );
	if( quit )
		PostMessage( MainhWnd, WM_CLOSE, 0, 0L );
	return;
}
#endif

void pokey_update( void )
{
	if( bSoundIsPaused )
		return;

	if( --unSkipPokeyUpdate )
		return;
	else
	{
		unsigned long	unNewPos = 0;

		unSkipPokeyUpdate = nSkipUpdateDefault;
		unNewPos = unSampleSize * unUpdateCount / unRealUpdatesPerSample;
#ifdef USE_DSOUND
		if( ulSoundState & SOUND_DIRECTSOUND )
		{
			HRESULT hr; 
			LPVOID lpvPtr1; 
			DWORD dwBytes1; 

			if( lpDsbCur )
			{
				hr = lpDsbCur->lpVtbl->Lock(lpDsbCur, unSamplePos, unNewPos - unSamplePos, &lpvPtr1, &dwBytes1, 0, 0, 0); 
				if( hr == DSERR_BUFFERLOST) 
				{ 
					lpDsbCur->lpVtbl->Restore(lpDsbCur);
					hr = lpDsbCur->lpVtbl->Lock(lpDsbCur, unSamplePos, unNewPos - unSamplePos, &lpvPtr1, &dwBytes1, 0, 0, 0); 
				}
				if( hr == DS_OK )
				{ 
					Pokey_process( lpvPtr1, (short)dwBytes1 );

					// Release the data back to DirectSound. 
					hr = lpDsbCur->lpVtbl->Unlock(lpDsbCur, lpvPtr1, dwBytes1, 0, 0); 
					if( hr != DS_OK )
						ShowDSoundError( IDS_DSERR_UNLOCK, hr, TRUE );
				}
				else
					ShowDSoundError( IDS_DSERR_LOCK, hr, TRUE );
			}
		}
		else
#endif
			Pokey_process( pcCurSoundBuf + unSamplePos, (short)(unNewPos - unSamplePos)  );
		unSamplePos = unNewPos;
		unUpdateCount++;
	}
}

#ifdef USE_DSOUND
static BOOL DetermineHardwareCaps(LPDIRECTSOUND lpDirectSound, BOOL bIsPrimary)
{
    DSCAPS dscaps;
    HRESULT hResult;
	char	failed[LOADSTRING_STRING_SIZE];
	char	error[LOADSTRING_STRING_SIZE * 2];
    
	dscaps.dwSize = sizeof(DSCAPS);
	failed[0] = 0;
	
    hResult = lpDirectSound->lpVtbl->GetCaps(lpDirectSound, &dscaps);
	
    if( hResult == DS_OK ) 
	{
		if( bIsPrimary )
		{	
			if( !(dscaps.dwFlags & DSCAPS_PRIMARY8BIT) )
			{
				LoadString( NULL, IDS_DSOUND_NO_PRIMARY, gcErrorString, LOADSTRING_STRING_SIZE );
				strcat( failed, gcErrorString );
			}
			if( !(dscaps.dwFlags & DSCAPS_PRIMARYMONO) )
			{
				LoadString( NULL, IDS_DSOUND_NO_MONO, gcErrorString, LOADSTRING_STRING_SIZE );
				strcat( failed, gcErrorString );
			}
		}
		if( dscaps.dwFlags & DSCAPS_EMULDRIVER  )
		{
			LoadString( NULL, IDS_DSOUND_NO_DRIVER, gcErrorString, LOADSTRING_STRING_SIZE );
			strcat( failed, gcErrorString );
		}
		
		if( failed[0] )
		{
			LoadString( NULL, IDS_DSOUND_INIT_ERROR, gcErrorString, LOADSTRING_STRING_SIZE );
			sprintf( error, gcErrorString, failed );
			LoadString( NULL, IDS_DSOUND_ERROR_HDR, gcErrorString, LOADSTRING_STRING_SIZE );
			MessageBox( MainhWnd, error, gcErrorString, MB_OK );
			ulSoundState = SOUND_NOSOUND;
			ulSoundState &= ~SOUND_FORCE_PRIMARY;
			WriteRegDWORD( NULL, REG_SOUND_STATE, ulSoundState);
			return FALSE;
		}
    }
	else
	{
		ShowDSoundError( IDS_DSERR_QUERY, hResult, TRUE );
		return FALSE;
	}
	return TRUE;
}
#endif

int Sound_Initialise( void )
{
	bSoundIsPaused = FALSE;
	unSkipPokeyUpdate = nSkipUpdateDefault;
	unSamplePos = 0;
	unUpdateCount = 1;
	if( !pcSndBuf )
		pcSndBuf = calloc( 1, NUM_SOUND_BUFS * DEFAULT_SOUND_BUFFER_SIZE );

	if( tv_mode == TV_PAL )
	{
		unSampleSize = nSoundRate / 50 * SOUND_LATENCY_IN_FRAMES;
		Pokey_sound_init (FREQ_17_EXACT, (short)nSoundRate, (char)1);
		unRealUpdatesPerSample = 312 * SOUND_LATENCY_IN_FRAMES / unSkipPokeyUpdate;
	}
	else
	{
		unSampleSize = nSoundRate / 60 * SOUND_LATENCY_IN_FRAMES;
		Pokey_sound_init (FREQ_17_EXACT, (short)nSoundRate, (char)1);
		unRealUpdatesPerSample = 262 * SOUND_LATENCY_IN_FRAMES / unSkipPokeyUpdate;
	}

	pcCurSoundBuf = &pcSndBuf[0];

	if( ulSoundState & SOUND_NOSOUND )
	{
		Atari_PlaySound = SndMngr_NoSound;
		return 1;
	}

	/* Set this up for PCM, 1 channel, 8 bits unsigned samples */
	ZeroMemory( &WaveFormat, sizeof( WAVEFORMATEX ) );
	WaveFormat.wFormatTag = WAVE_FORMAT_PCM;
	WaveFormat.nChannels = 1;
	WaveFormat.nSamplesPerSec = nSoundRate;
	WaveFormat.nBlockAlign = 1;
	WaveFormat.nAvgBytesPerSec = nSoundRate; /* nSamplesPerSec * nBlockAlign */
	WaveFormat.wBitsPerSample = 8;
	WaveFormat.cbSize = 0;

	if( ulSoundState & SOUND_MMSOUND )
	{
		MMRESULT	mmResult = MMSYSERR_NOERROR;
		int i;

		for( i = 0; i < NUM_SOUND_BUFS; i++ )
			ZeroMemory( &WaveHDR[i], sizeof( WAVEHDR ) );

		if( !hWaveOut )
			mmResult = waveOutOpen( &hWaveOut, WAVE_MAPPER, &WaveFormat, 0, 0, CALLBACK_NULL );

		if( mmResult != MMSYSERR_NOERROR )
			ShowMMError( IDS_MMERR_OPEN, mmResult, TRUE );
		else
		{
			unSwitchSndBuf = 1;

			for( i = 0; i < NUM_SOUND_BUFS; i++ )
			{
				WaveHDR[i].lpData = &pcSndBuf[i * DEFAULT_SOUND_BUFFER_SIZE];
				WaveHDR[i].dwBufferLength = unSampleSize;
				WaveHDR[i].dwBytesRecorded = 0;
				WaveHDR[i].dwUser = 0;
				WaveHDR[i].dwFlags = 0;
				WaveHDR[i].dwLoops = 1;
			}
			mmResult = waveOutPrepareHeader( hWaveOut, &WaveHDR[unSwitchSndBuf], sizeof( WaveHDR));
			if( mmResult != MMSYSERR_NOERROR )
				ShowMMError( IDS_MMERR_INIT_HDR, mmResult, TRUE );
			waveOutUnprepareHeader( hWaveOut, &WaveHDR[unSwitchSndBuf], sizeof( WAVEHDR));

			Atari_PlaySound = SndMngr_MMSound;
			Sound_SetVolume();

			if( ulMiscStates & ATARI_FULL_SPEED )
				Clear_Sound( FALSE );
			return 1;
		}
		return 0;
	}

#ifdef USE_DSOUND
	/* This should always be true at this point, but.... */
	if( ulSoundState & SOUND_DIRECTSOUND )
	{
		BOOL	bHwCheck = FALSE;
		HRESULT	hResult = DS_OK;

		unSwitchSndBuf = 0;
		if( !lpDSound )
			hResult = DirectSoundCreate( NULL, &lpDSound, NULL );
		if( hResult != DS_OK )
		{
			ShowDSoundError( IDS_DSERR_CREATE_OBJ, hResult, TRUE );
			return 0;
		}

		if( (ulSoundState & SOUND_CUSTOM_RATE) || (ulSoundState & SOUND_FORCE_PRIMARY) )
			bHwCheck = DetermineHardwareCaps( lpDSound, TRUE );
		else
			bHwCheck = DetermineHardwareCaps( lpDSound, FALSE );

		if( bHwCheck == FALSE )
			Clear_Sound( TRUE );

		if( lpDSound )
		{
			DSBUFFERDESC dsbdesc; 

			if( !(ulSoundState & SOUND_CUSTOM_RATE) && !(ulSoundState & SOUND_FORCE_PRIMARY) )
			{
//				Atari_PlaySound = SndMngr_DsoundPrimary;
				Atari_PlaySound = SndMngr_DsoundCoop;

				hResult = IDirectSound_SetCooperativeLevel( lpDSound, MainhWnd, DSSCL_NORMAL );
				if( hResult != DS_OK )
				{
					ShowDSoundError( IDS_DSERR_SET_COOP, hResult, TRUE );
					return 0;
				}
			}

			// Set up DSBUFFERDESC structure. 
			ZeroMemory( &dsbdesc, sizeof( DSBUFFERDESC ) );
			dsbdesc.dwSize = sizeof(DSBUFFERDESC); 

			if( (ulSoundState & SOUND_CUSTOM_RATE) || (ulSoundState & SOUND_FORCE_PRIMARY) )
			{
				dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
				dsbdesc.dwBufferBytes = 0; // Buffer size is determined by sound hardware.
				dsbdesc.lpwfxFormat = NULL; // Must be NULL for primary buffers.

//				Atari_PlaySound = SndMngr_DsoundPrimary;
				Atari_PlaySound = SndMngr_DsoundCoop;

				// Obtain write-primary cooperative level.
				hResult = lpDSound->lpVtbl->SetCooperativeLevel(lpDSound, MainhWnd, DSSCL_WRITEPRIMARY);
//				hResult = lpDSound->lpVtbl->SetCooperativeLevel(lpDSound, MainhWnd, DSSCL_PRIORITY);
				
				if( hResult != DS_OK )
				{
					ShowDSoundError( IDS_DSERR_SET_PRIORITY, hResult, TRUE );
					return 0;
				}

				// Succeeded! Try to create buffer.
				hResult = lpDSound->lpVtbl->CreateSoundBuffer(lpDSound, &dsbdesc, &lpDsPrimary, NULL);
				if( hResult != DS_OK )
				{
					ShowDSoundError( IDS_DSERR_CREATE_PRIMARY, hResult, TRUE );
					return 0;
				}
				// Succeeded! Set primary buffer to desired format.
				hResult = lpDsPrimary->lpVtbl->SetFormat(lpDsPrimary, &WaveFormat);
				if( hResult != DS_OK )
				{
					ShowDSoundError( IDS_DSERR_PRI_FORMAT, hResult, TRUE );
					return 0;
				}

				hResult = lpDsPrimary->lpVtbl->Play( lpDsPrimary, 0, 0, DSBPLAY_LOOPING/*0*/ );
				if( hResult != DS_OK )
				{
					ShowDSoundError( IDS_DSERR_PRI_LOOP, hResult, TRUE );
					return 0;
				}

				ZeroMemory( &dsbdesc, sizeof( DSBUFFERDESC ) );
				dsbdesc.dwSize = sizeof(DSBUFFERDESC); 
			}
			
			// Set up DSBUFFERDESC structure. 
			ZeroMemory( &dsbdesc, sizeof( DSBUFFERDESC ) );
			dsbdesc.dwSize = sizeof(DSBUFFERDESC); 

			/* This is for a secondary buffer */
			// Need default controls (pan, volume, frequency). 
			dsbdesc.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLVOLUME | DSBCAPS_GETCURRENTPOSITION2; 
			// 2-sample buffer (put some extra on the end because we'll rarely refresh at exactly
			// the right time
			dsbdesc.dwBufferBytes = unSampleSize; 
			dsbdesc.lpwfxFormat = &WaveFormat;

			_ASSERT( !lpDsb1 );
			hResult = lpDSound->lpVtbl->CreateSoundBuffer(lpDSound, &dsbdesc, &lpDsb1, NULL); 
			if( hResult != DS_OK ) 
			{ 
				lpDsb1 = NULL;
				ShowDSoundError( IDS_DSERR_CREATE_BUFF, hResult, TRUE );
				return 0;
			} 
			lpDsb1->lpVtbl->SetCurrentPosition( lpDsb1, 0 );

			_ASSERT( !lpDsb2 );
			hResult = lpDSound->lpVtbl->CreateSoundBuffer(lpDSound, &dsbdesc, &lpDsb2, NULL); 
			if( hResult != DS_OK ) 
			{ 
				lpDsb2 = NULL;
				ShowDSoundError( IDS_DSERR_CREATE_BUFF, hResult, TRUE );
				return 0;
			} 
			lpDsb2->lpVtbl->SetCurrentPosition( lpDsb2, 0 );
			lpDsbCur = lpDsb1;
			
		}				
	}
	Sound_SetVolume( );
	if( ulMiscState & ATARI_FULL_SPEED )
		Clear_Sound( FALSE );
#endif
	return 1;
}

static void SndMngr_NoSound( void )
{
	if( bSoundIsPaused )
	{
		wait_for_vbi();
		return;
	}

	if( ++nFrameCount > SOUND_LATENCY_IN_FRAMES )
	{
		if( unSamplePos < unSampleSize )
			Pokey_process( pcCurSoundBuf + unSamplePos, (short)(unSampleSize - unSamplePos) );
		if( sndoutput )
			fwrite( pcCurSoundBuf, unSampleSize, 1, sndoutput );
		nFrameCount = 1;
		unUpdateCount = 1;
		unSamplePos = 0;

		unSwitchSndBuf++;
		_ASSERT( NUM_SOUND_BUFS >= unSwitchSndBuf );
		if( unSwitchSndBuf == NUM_SOUND_BUFS )
			unSwitchSndBuf = 0;

		pcCurSoundBuf = &pcSndBuf[ unSwitchSndBuf * DEFAULT_SOUND_BUFFER_SIZE ];
	}
	wait_for_vbi();

	return;
}

#ifdef USE_DSOUND
static void SndPlay_DsoundCoop( void )
{
	HRESULT hr; 
	DWORD dwBytes1; 
	_ASSERT( unSamplePos < DEFAULT_SOUND_BUFFER_SIZE );

	if( unSamplePos < unSampleSize )
	{
		LPVOID lpvPtr1; 

		hr = lpDsbCur->lpVtbl->Lock(lpDsbCur, unSamplePos, unSampleSize - unSamplePos, &lpvPtr1, &dwBytes1, 0, 0, 0); 
		// If DSERR_BUFFERLOST is returned, restore and retry lock. 
		if( hr == DSERR_BUFFERLOST) 
		{ 
			lpDsbCur->lpVtbl->Restore(lpDsbCur);
			hr = lpDsbCur->lpVtbl->Lock(lpDsbCur, unSamplePos, unSampleSize - unSamplePos, &lpvPtr1, &dwBytes1, 0, 0, 0); 
		}
		if( hr == DS_OK )
		{ 
			Pokey_process( lpvPtr1, (short)dwBytes1 );

			// Release the data back to DirectSound. 
			hr = lpDsbCur->lpVtbl->Unlock(lpDsbCur, lpvPtr1, dwBytes1, 0, 0); 
			if( hr != DS_OK )
				ShowDSoundError( IDS_DSERR_UNLOCK, hr, TRUE );
		}
		else
			ShowDSoundError( IDS_DSERR_RESTORE, hr, TRUE );
	}
	if( sndoutput )
		fwrite( pcCurSoundBuf, unSampleSize, 1, sndoutput );

	lpDsbCur->lpVtbl->Play( lpDsbCur, 0, 0, DSBPLAY_LOOPING );

	wait_for_vbi();
}

static void SndMngr_DsoundCoop( void )
{
	if( ++nFrameCount > SOUND_LATENCY_IN_FRAMES )
	{
		SndPlay_DsoundCoop();
//		if( lpDsbCur == lpDsb1 )
//			lpDsbCur = lpDsb2;
//		else
			lpDsbCur = lpDsb1;

		nFrameCount = 1;
		unUpdateCount = 1;
		unSamplePos = 0;

		unSwitchSndBuf++;
		_ASSERT( NUM_SOUND_BUFS >= unSwitchSndBuf );
		if( unSwitchSndBuf == NUM_SOUND_BUFS )
			unSwitchSndBuf = 0;

		pcCurSoundBuf = &pcSndBuf[ 0 ];
	}
	else
		wait_for_vbi();
}

static void SndPlay_DsoundPrimary( void )
{
	LPVOID lpvPtr1, lpvPtr2; 
	DWORD dwBytes1,dwBytes2; 
	HRESULT hr; 

	_ASSERT( unSamplePos < DEFAULT_SOUND_BUFFER_SIZE );

	if( unSamplePos < unSampleSize )
		Pokey_process( pcCurSoundBuf + unSamplePos, unSampleSize - unSamplePos );

	Pokey_process( pcCurSoundBuf + unSampleSize, POKEY_SLOP_BYTES );
	unSamplePos = unSampleSize + POKEY_SLOP_BYTES;
	
	if( sndoutput )
		fwrite( pcCurSoundBuf, unSampleSize, 1, sndoutput );

	hr = lpDsPrimary->lpVtbl->Lock(lpDsPrimary, 0, unSamplePos, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, DSBLOCK_FROMWRITECURSOR); 
	// If DSERR_BUFFERLOST is returned, restore and retry lock. 
	if( hr == DSERR_BUFFERLOST) 
	{ 
		hr = lpDsPrimary->lpVtbl->Restore(lpDsPrimary);
		//Restoring almost never works here, just close the sound channel and re-open it.
		if( hr == DSERR_BUFFERLOST )
		{
			Clear_Sound( TRUE );
			Sound_Initialise();
			hr = lpDsPrimary->lpVtbl->Lock(lpDsPrimary, 0, unSamplePos, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, DSBLOCK_FROMWRITECURSOR);
		}
	}
	if( hr == DS_OK )
	{ 
		// Write to pointer
		CopyMemory(lpvPtr1, pcCurSoundBuf, dwBytes1); 

		if( lpvPtr2 )
			CopyMemory( lpvPtr2, pcCurSoundBuf + dwBytes1, dwBytes2 );

		// Release the data back to DirectSound. 
		lpDsPrimary->lpVtbl->Unlock(lpDsPrimary, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2); 
	}
	else
		ShowDSoundError( IDS_DSERR_RESTORE, hr, TRUE );

	wait_for_vbi();
}


static void SndMngr_DsoundPrimary( void )
{
	if( ++nFrameCount > SOUND_LATENCY_IN_FRAMES )
	{
		SndPlay_DsoundPrimary();
		nFrameCount = 1;
		unUpdateCount = 1;
		unSamplePos = 0;
		pcCurSoundBuf = pcSndBuf;
	}
	else
		wait_for_vbi();
}
#endif /* USE_DSOUND */

__inline static void SndPlay_MMSound( )
{
	MMRESULT	mmResult;

	_ASSERT( unSamplePos < DEFAULT_SOUND_BUFFER_SIZE );

	if( unSamplePos < unSampleSize )
		Pokey_process( pcCurSoundBuf + unSamplePos, (short)(unSampleSize - unSamplePos) );

	if( sndoutput )
		fwrite( pcCurSoundBuf, unSampleSize, 1, sndoutput );
	
	WaveHDR[unSwitchSndBuf].dwBufferLength = unSampleSize;
	waveOutUnprepareHeader( hWaveOut, &WaveHDR[unSwitchSndBuf], sizeof( WAVEHDR));
	
	mmResult = waveOutPrepareHeader( hWaveOut, &WaveHDR[unSwitchSndBuf], sizeof( WAVEHDR));
	if( mmResult != MMSYSERR_NOERROR )
		ShowMMError( IDS_MMERR_PREP_HDR, mmResult, TRUE );

	wait_for_vbi();

	if( !bSoundIsPaused )
		waveOutWrite( hWaveOut, &WaveHDR[unSwitchSndBuf], sizeof( WAVEHDR) );
}

static void SndMngr_MMSound( void )
{
	_ASSERT( pcCurSoundBuf < &pcSndBuf[0] + unSwitchSndBuf * DEFAULT_SOUND_BUFFER_SIZE + DEFAULT_SOUND_BUFFER_SIZE );
	_ASSERT( SOUND_LATENCY_IN_FRAMES + 1 >= nFrameCount );
	if( ++nFrameCount > SOUND_LATENCY_IN_FRAMES )
	{
		_ASSERT( unSamplePos != 0 );
		SndPlay_MMSound();
		nFrameCount = 1;
		unUpdateCount = 1;
		unSamplePos = 0;

		unSwitchSndBuf++;
		_ASSERT( NUM_SOUND_BUFS >= unSwitchSndBuf );
		if( unSwitchSndBuf == NUM_SOUND_BUFS )
			unSwitchSndBuf = 0;

		pcCurSoundBuf = &pcSndBuf[ unSwitchSndBuf * DEFAULT_SOUND_BUFFER_SIZE ];
	}
	else
		wait_for_vbi();
}

BOOL Sound_VolumeCapable( void )
{
	WAVEOUTCAPS		woc;
	BOOL bReturn = TRUE;

	memset( &woc, 0, sizeof( WAVEOUTCAPS ) );
	if( hWaveOut )
	{
		waveOutGetDevCaps( (unsigned int)hWaveOut, &woc, sizeof( WAVEOUTCAPS ) );
	}
	else
	{
		HWAVEOUT	hLocalWave;
		MMRESULT	mmResult;

		mmResult = waveOutOpen( &hLocalWave, WAVE_MAPPER, &WaveFormat, 0, 0, CALLBACK_NULL );
		if( mmResult == MMSYSERR_NOERROR )
		{
			waveOutGetDevCaps( (unsigned int)hLocalWave, &woc, sizeof( WAVEOUTCAPS ) );
			waveOutClose( hLocalWave );
		}
	}

	if( woc.dwSupport & WAVECAPS_VOLUME )
		bReturn = TRUE;
	else
		bReturn = FALSE;

	return bReturn;
}

void Sound_SetVolume( void )
{
	if( (ulSoundState & SOUND_MMSOUND) && hWaveOut!=NULL )
	{
		if( Sound_VolumeCapable() )
		{
			MMRESULT mmResult;
			DWORD			dwVolume, dwTempVolume;

			/* Save the volume setting so we can put it back later */
			if( !dwStartVolume )
				mmResult = waveOutGetVolume( hWaveOut, &dwStartVolume );

			/* Hiword is the right channel, low word is the left channel */
			dwVolume = (HIWORD( dwStartVolume ) + HIWORD( dwStartVolume ) / 100 * nSoundVol) * 65536;
			dwVolume += LOWORD(dwStartVolume) + LOWORD( dwStartVolume ) / 100 * nSoundVol;

			mmResult = waveOutSetVolume( hWaveOut, dwVolume );
			if( mmResult != MMSYSERR_NOERROR )
				ShowMMError( IDS_MMERR_SET_VOLUME, mmResult, TRUE );

			/* It's possible this wave device doesn't support 16 bits of volume control, so we'll check
			   the result of the set with waveOutGetVolume, if it's less than what we set, we know the
			   new max and we'll scale to that */

			waveOutGetVolume( hWaveOut, &dwTempVolume );
			if( dwTempVolume < dwVolume )
			{
				float percentage = ((float)dwTempVolume/dwVolume);
				dwVolume = (unsigned long)(dwVolume * percentage);
				waveOutSetVolume( hWaveOut, dwVolume );
			}
		}
	}

#ifdef USE_DSOUND
	if( ulSoundState & SOUND_DIRECTSOUND )
	{
		HRESULT	hResult = DS_OK;

		if( lpDsb1 )
			hResult = lpDsb1->lpVtbl->SetVolume( lpDsb1, nSoundVol * 100 );
		if( lpDsb2 )
			hResult = lpDsb2->lpVtbl->SetVolume( lpDsb2, nSoundVol * 100 );
	}
#endif
}

void Clear_Sound( BOOL bPermanent )
{
	bSoundIsPaused = TRUE;

	/*Are we shutting down everything, or just killing the 
	  sound that is playing currently? */
	if( bPermanent )
	{
		int i;
		Atari_PlaySound = SndMngr_NoSound;

		if( sndoutput )
			fclose( sndoutput );
		sndoutput = NULL;

		if( hWaveOut )
		{
			if( dwStartVolume )
				waveOutSetVolume( hWaveOut, dwStartVolume );
			dwStartVolume = 0;

			waveOutReset( hWaveOut );
			for( i=0; i < NUM_SOUND_BUFS; i++ )
			{
				waveOutUnprepareHeader( hWaveOut, &WaveHDR[i], sizeof( WAVEHDR));
				WaveHDR[i].lpData = NULL;
			}
			waveOutClose( hWaveOut );
			hWaveOut = 0;
		}

#ifdef USE_DSOUNDS
		if( lpDsb1 )
			lpDsb1->lpVtbl->Release( lpDsb1 );
		lpDsb1 = NULL;

		if( lpDsb2 )
			lpDsb2->lpVtbl->Release( lpDsb2 );
		lpDsb2 = NULL;
		lpDsbCur = NULL;

		if( lpDsPrimary )
			lpDsPrimary->lpVtbl->Release( lpDsPrimary );
		lpDsPrimary = NULL;

		if( lpDSound )
			lpDSound->lpVtbl->Release( lpDSound );
		lpDSound = NULL;
#endif

		if( pcSndBuf )
			free( pcSndBuf );
		pcSndBuf = NULL;
		return;
	}
	else
	{
		if( hWaveOut )
			waveOutPause( hWaveOut );

#ifdef USE_DSOUND
		if( ((ulSoundState & SOUND_CUSTOM_RATE) || (ulSoundState & SOUND_FORCE_PRIMARY)) && lpDsPrimary )
		{
			HRESULT hResult = lpDsPrimary->lpVtbl->Stop( lpDsPrimary );
			if( hResult == DSERR_BUFFERLOST) 
			{ 
				lpDsPrimary->lpVtbl->Restore(lpDsPrimary);
				hResult = lpDsPrimary->lpVtbl->Stop( lpDsPrimary );
			}
		}

		if( lpDsb1 )
			lpDsb1->lpVtbl->Stop( lpDsb1 );
		if( lpDsb2 )
			lpDsb2->lpVtbl->Stop( lpDsb2 );
#endif
	}
}

void Restart_Sound( void )
{
	if( ulMiscStates & ATARI_FULL_SPEED )
	{
		bSoundIsPaused = TRUE;
		return;
	}

	bSoundIsPaused = FALSE;

	if( hWaveOut )
		waveOutRestart( hWaveOut );

#ifdef USE_DSOUND_PRIMARY_WRITE
	if( lpDsPrimary && ((ulSoundState & SOUND_CUSTOM_RATE) || (ulSoundState & SOUND_FORCE_PRIMARY)) )
	{
		HRESULT hResult = lpDsPrimary->lpVtbl->Play( lpDsPrimary, 0, 0, DSBPLAY_LOOPING );
		if( hResult == DSERR_BUFFERLOST) 
		{ 
			lpDsPrimary->lpVtbl->Restore(lpDsPrimary);
			hResult = lpDsPrimary->lpVtbl->Play( lpDsPrimary, 0, 0, DSBPLAY_LOOPING );
		}
	}
#endif
}

void Atari_AUDC (int channel, int byte)
{
	channel--;
	Update_pokey_sound ((WORD)(0xd201 + channel + channel), (BYTE)byte, 0, 4);
}

void Atari_AUDF (int channel, int byte)
{
	channel--;
	Update_pokey_sound ((WORD)(0xd200  + channel + channel), (BYTE)byte, 0, 4);
}

void Atari_AUDCTL (int byte)
{
	Update_pokey_sound ((WORD)0xd208, (BYTE)byte, 0, 4);
}

#ifdef USE_DSOUND
static BOOL GetDSErrorString( HRESULT hResult, LPSTR lpszErrorBuff, DWORD cchError)
{
    DWORD  cLen;
    LPTSTR lpszError;
    TCHAR  szMsg[256];

    // Check parameters
    if (!lpszErrorBuff || !cchError)
    {
        // Error, invalid parameters
        return FALSE;
    }

    switch (hResult)
    {
#ifdef VERBOSE_DIRECTX_ERROR
		case DS_OK:
			//The request completed successfully. 
			lpszError = TEXT("DS_OK");
			break;

		case DSERR_ALLOCATED:
			//The request failed because resources, such as a priority level, were already in use by another caller. 
			lpszError = TEXT("DSERR_ALLOCATED");
			break;

		case DSERR_ALREADYINITIALIZED:
			//The object is already initialized. 
			lpszError = TEXT("DSERR_ALREADYINITIALIZED");
			break;

		case DSERR_BADFORMAT:
			//The specified wave format is not supported. 
			lpszError = TEXT("DSERR_BADFORMAT");
			break;

		case DSERR_BUFFERLOST:
			//The buffer memory has been lost and must be restored. 
			lpszError = TEXT("DSERR_BUFFERLOST");
			break;

		case DSERR_CONTROLUNAVAIL:
			//The control (volume, pan, and so forth) requested by the caller is not available. 
			lpszError = TEXT("DSERR_CONTROLUNAVAIL");
			break;

		case DSERR_GENERIC:
			//An undetermined error occurred inside the DirectSound subsystem. 
			lpszError = TEXT("DSERR_GENERIC");
			break;

		case DSERR_INVALIDCALL:
			//This function is not valid for the current state of this object. 
			lpszError = TEXT("DSERR_INVALIDCALL");
			break;

		case DSERR_INVALIDPARAM:
			//An invalid parameter was passed to the returning function. 
			lpszError = TEXT("DSERR_INVALIDPARAM");
			break;
		
		case DSERR_NOAGGREGATION:
			//The object does not support aggregation. 
			lpszError = TEXT("DSERR_NOAGGREGATION");
			break;

		case DSERR_NODRIVER:
			//No sound driver is available for use. 
			lpszError = TEXT("DSERR_NODRIVER");
			break;

		case DSERR_NOINTERFACE:
			//The requested COM interface is not available. 
			lpszError = TEXT("DSERR_NOINTERFACE");
			break;
			
		case DSERR_OTHERAPPHASPRIO:
			//Another application has a higher priority level, preventing this call from succeeding 
			lpszError = TEXT("DSERR_OTHERAPPHASPRIO");
			break;

		case DSERR_OUTOFMEMORY:
			//The DirectSound subsystem could not allocate sufficient memory to complete the caller's request. 
			lpszError = TEXT("DSERR_OUTOFMEMORY");
			break;

		case DSERR_PRIOLEVELNEEDED:
			//The caller does not have the priority level required for the function to succeed. 
			lpszError = TEXT("DSERR_PRIOLEVELNEEDED");
			break;

		case DSERR_UNINITIALIZED:
			//The IDirectSound::Initialize method has not been called or has not been called successfully before other methods were called. 
			lpszError = TEXT("DSERR_UNINITIALIZED");
			break;

		case DSERR_UNSUPPORTED:
			//The function called is not supported at this time. 
			lpszError = TEXT("DSERR_UNSUPPORTED");
			break;

#endif	/* VERBOSE_DIRECTX_ERROR */

		default:
			// Unknown DS Error
			wsprintf (szMsg, "Error #%ld", (DWORD)(hResult & 0x0000FFFFL));
			lpszError = szMsg;
			break;
	}
    // Copy DS Error string to buff
    cLen = strlen (lpszError);
    if (cLen >= cchError)
    {
        cLen = cchError - 1;
    }

    if (cLen)
    {
        strncpy (lpszErrorBuff, lpszError, cLen);
        lpszErrorBuff[cLen] = 0;
    }

    return TRUE;
}
#endif /* USE_DSOUND */

static BOOL GetMMErrorString( MMRESULT mmResult, LPSTR lpszErrorBuff, DWORD cchError)
{
    DWORD  cLen;
    LPTSTR lpszError;
    TCHAR  szMsg[256];

    // Check parameters
    if (!lpszErrorBuff || !cchError)
    {
        // Error, invalid parameters
        return FALSE;
    }

    switch ( mmResult )
    {
//#ifdef VERBOSE_DIRECTX_ERROR
		case	MMSYSERR_NOERROR:
			/* no error */ 
			lpszError = TEXT("MMSYSERR_NOERROR");
			break;

		case	WAVERR_UNPREPARED:
			/* Wave header is not prepared for output */
			lpszError = TEXT( "WAVERR_UNPREPARED");
			break;

		case	MMSYSERR_ERROR:
			/* unspecified error */
			lpszError = TEXT("MMSYSERR_ERROR");
			break;

		case	MMSYSERR_BADDEVICEID:
			/* device ID out of range */
			lpszError = TEXT("MMSYSERR_BADDEVICEID");
			break;

		case	MMSYSERR_NOTENABLED:
			/* driver failed enable */
			lpszError = TEXT("MMSYSERR_NOTENABLED");
			break;

		case	MMSYSERR_ALLOCATED:
			/* device already allocated */
			lpszError = TEXT("MMSYSERR_ALLOCATED");
			break;

		case	MMSYSERR_INVALHANDLE:
			/* device handle is invalid */
			lpszError = TEXT("MMSYSERR_INVALHANDLE");
			break;

		case	MMSYSERR_NODRIVER:
			/* no device driver present */
			lpszError = TEXT("MMSYSERR_NODRIVER");
			break;

		case	MMSYSERR_NOMEM:
			/* memory allocation error */
			lpszError = TEXT("MMSYSERR_NOMEM");
			break;

		case	MMSYSERR_NOTSUPPORTED:
			/* function isn't supported */
			lpszError = TEXT("MMSYSERR_NOTSUPPORTED");
			break;

		case	MMSYSERR_BADERRNUM:
			/* error value out of range */
			lpszError = TEXT("MMSYSERR_BADERRNUM");
			break;

		case	MMSYSERR_INVALFLAG:
			/* invalid flag passed */
			lpszError = TEXT("MMSYSERR_INVALFLAG");
			break;

		case	MMSYSERR_INVALPARAM:
			/* invalid parameter passed */
			lpszError = TEXT("MMSYSERR_INVALPARAM");
			break;

		case	MMSYSERR_HANDLEBUSY:
			/* handle being used */
			lpszError = TEXT("MMSYSERR_HANDLEBUSY");
			break;

		case	MMSYSERR_INVALIDALIAS:
			/* specified alias not found */
			lpszError = TEXT("MMSYSERR_INVALIDALIAS");
			break;

		case	MMSYSERR_BADDB:
			/* bad registry database */
			lpszError = TEXT("MMSYSERR_BADDB");
			break;

		case	MMSYSERR_KEYNOTFOUND:
			/* registry key not found */
			lpszError = TEXT("MMSYSERR_KEYNOTFOUND");
			break;

		case	MMSYSERR_READERROR:
			/* registry read error */
			lpszError = TEXT("MMSYSERR_READERROR");
			break;

		case	MMSYSERR_WRITEERROR:
			/* registry write error */
			lpszError = TEXT("MMSYSERR_WRITEERROR");
			break;

		case	MMSYSERR_DELETEERROR:
			/* registry delete error */
			lpszError = TEXT("MMSYSERR_DELETEERROR");
			break;

		case	MMSYSERR_VALNOTFOUND:
			/* registry value not found */
			lpszError = TEXT("MMSYSERR_VALNOTFOUND");
			break;

		case	MMSYSERR_NODRIVERCB:
			/* driver does not call DriverCallback */
			lpszError = TEXT("MMSYSERR_NODRIVERCB");
			break;

//#endif /* VERBOSE_DIRECTX_ERROR */

		default:
			// Unknown MM Error
			wsprintf (szMsg, "Unknown Error #%ld", (DWORD)(mmResult));
			lpszError = szMsg;
			break;
	}

    // Copy MM Error string to buff
    cLen = strlen (lpszError);
    if (cLen >= cchError)
    {
        cLen = cchError - 1;
    }

    if (cLen)
    {
        strncpy (lpszErrorBuff, lpszError, cLen);
        lpszErrorBuff[cLen] = 0;
    }

    return TRUE;
}
