#include "pch.hpp"
//==========================================================================//
// File:	 Sound Renderer.cpp												 //
// Project:  GameOS					                                         //
// Contents: Direct Sound manager											 //
//---------------------------------------------------------------------------//
// Date      Who    Modification                                             //
// 01/23/98  JTR    Created													 //
//																			 //
//---------------------------------------------------------------------------//
// Copyright (C) 1999 Microsoft Corp.						                 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//
//
#include "Sound Renderer.hpp"
#include "Sound DS3DChannel.hpp"
#include "ErrorHandler.hpp"
#include "Windows.hpp"
#include "DirectX.hpp"

bool DisableSoundSystem = false;
HANDLE SoundTimerThread=0;
HANDLE KillSoundEvent=0;

extern HGOSHEAP ParentGameOSHeap;

extern SoundRendererData SoundRenderer;
extern LinkedList<SoundResource *> * g_soundResources;
extern CRITICAL_SECTION g_soundCritSec;
extern float TimeAudio;

// this is for handling logged mode
extern DWORD gFreezeLogic;

__int64 GetDriverFileDetails( FixedLengthString& Buffer, char* DriverName, bool SoundInfo=0 );
extern char* DecNumber( DWORD Value, bool DisplayMeg=1 );
extern char * Day[];
BOOL CALLBACK DSEnumProc(LPGUID lpGuid, LPCSTR lpDesc, LPCSTR lpMod, void * lpContext);

HGOSHEAP SoundHeap=0;


void SoundRendererInstall(int num_Channels)
{
	SPEW(( GROUP_DIRECTSOUND, "SoundRendererInstall()" ));

	memset(&SoundRenderer,0,sizeof(SoundRenderer));

	if( 0==SoundHeap )
		SoundHeap = gos_CreateMemoryHeap("Sound", 0, ParentGameOSHeap);

	g_soundResources = new(SoundHeap) LinkedList<SoundResource *>;

#ifdef LAB_ONLY
	memset(SoundRenderer.m_playHistory,0,sizeof(char*)*32);
#endif

	SoundRenderer.m_numDevices = 0;

	HRESULT hr;
	AllowFail=1;
	hr = wDirectSoundEnumerate( (LPDSENUMCALLBACK) DSEnumProc, 0 );
	AllowFail=0;
	if( SoundRenderer.m_numDevices )
		SoundRenderer.m_numDevices--;

	if( Environment.soundDisable == true || Environment.soundChannels==0 )
	{
		return;
	}

	gosASSERT(num_Channels <= MAX_SOUNDCHANNELS);
	SoundRenderer.m_Mixer = new(SoundHeap) DS3DSoundMixer();
	if ( Environment.soundDisable == true)
	{
		delete SoundRenderer.m_Mixer;
		SoundRenderer.m_Mixer = 0;
		SPEW(( GROUP_DIRECTSOUND, "SoundRendererInstall() - Couldn't enable" ));
		return;
	}

	gosAudio_SetChannelSlider( -1, gosAudio_FrontOrientation, 0.0f, 0.0f, 1.0f );
	gosAudio_SetChannelSlider( -1, gosAudio_TopOrientation, 0.0f, 1.0f, 0.0f );

	InitializeCriticalSection(&g_soundCritSec);

	SoundRenderer.m_workBuffer = (char *) gos_Malloc(4096, SoundHeap);

	SoundRendererCreateTimer();
	MusicManagerInstall();

	// run a quick test of the systems capabilities-- check for simple driver bugs
	if (Environment.soundMixInHardware == true && SoundRenderer.m_numChannels && !Environment.soundDisable && SoundRenderer.m_Mixer->m_dscaps.dwMaxHw3DAllBuffers != 0)
	{
		WAVEFORMATEX testWave;
		HGOSAUDIO hAudio;
		memset( &testWave, 0, sizeof(testWave) );
		testWave.wFormatTag      = WAVE_FORMAT_PCM; 
	    testWave.nChannels       = 1; 
		testWave.nSamplesPerSec  = 11025; 
	    testWave.wBitsPerSample  = 16; 
		testWave.nBlockAlign     = (WORD)(testWave.wBitsPerSample / 8 * testWave.nChannels);
	    testWave.nAvgBytesPerSec = testWave.nSamplesPerSec * testWave.nBlockAlign;

		char * testWaveData = (char *) gos_Malloc(32768);
		memset( testWaveData, 0x00, 32768 );
		gosAudio_CreateResource(&hAudio, gosAudio_UserMemory, "Initialization Test", (gosAudio_Format *)&testWave, testWaveData, 32768);

		for(int i=0; i < SoundRenderer.m_numChannels; i++)
		{
			gosAudio_AllocateChannelSliders(i, gosAudio_Position);
			gosAudio_AssignResourceToChannel(i, hAudio);
			wPlay(SoundRenderer.m_Channel[i]->m_Buffer, 0,0,0);
		}

		DWORD channelsLeft = SoundRenderer.m_numChannels;
		double duration = (double) (32768)  / testWave.nAvgBytesPerSec;
		bool timeExceeded = false;
		SPEW((GROUP_DIRECTSOUND, "Starting Hardware Mixing Test:"));
		double startTime = gos_GetHiResTime();
		double currentTime = 0;

		while(channelsLeft && !timeExceeded)
		{
			currentTime = gos_GetHiResTime();
			if (currentTime - startTime > 1.0f)
			{
				channelsLeft = 0;
				for(int i = 0; i < SoundRenderer.m_numChannels; i++)
				{
					// query all the channels, to see if they've completed.
					DWORD status;
					SpewSilence = true;
					wGetStatus(SoundRenderer.m_Channel[i]->m_Buffer, &status);
					SpewSilence = false;
					if ( status & DSBSTATUS_PLAYING)
						channelsLeft++;
				}
			}
			if (currentTime > startTime + duration + 1.0f )
				timeExceeded = true;
		}

		SPEW((GROUP_DIRECTSOUND, "Duration: %f %s", currentTime-startTime, channelsLeft ? "with channels remaining" : " "));

		for(i=0; i < SoundRenderer.m_numChannels; i++)
		{
			gosAudio_AssignResourceToChannel(i, NULL);
		}

		gosAudio_DestroyResource(&hAudio);
		gos_Free(testWaveData);

		if (timeExceeded)
		{
			// something is wrong, some sounds didn't stop!!!
			SPEW(( GROUP_DIRECTSOUND, "Hardware Mixing failed to played sound in alotted time. Switching to software." ));
			Environment.soundMixInHardware = 0;
		}
		else
		if (currentTime < startTime + duration)
		{
			// something is wrong, the frequency has been shifted up for some reason
			SPEW(( GROUP_DIRECTSOUND, "Hardware Mixing played sound too quickly. Switching to software." ));
			Environment.soundMixInHardware = 0;
		}
	}
	SPEW(( GROUP_DIRECTSOUND, "SoundRendererInstall() - Finished" ));
}



void SoundRendererEndFrame()
{
	if (SoundRenderer.m_isInitialized && !DisableSoundSystem)
		SoundRenderer.m_Mixer->Flush();
}



void SoundRendererUninstall()
{
	SPEW(( GROUP_DIRECTSOUND, "SoundRendererUninstall()" ));

	if (SoundRenderer.m_isInitialized)
		SoundRendererDestroyTimer();

	if (SoundRenderer.m_isInitialized)
	{
		for(int i = 0; i < SoundRenderer.m_numChannels; i++)
		{
			// gos_Free() don't forget to release filename, etc.
			if (SoundRenderer.m_Channel[i]) 
				delete SoundRenderer.m_Channel[i];
			SoundRenderer.m_Channel[i] = NULL;
		}
	}

	if (g_soundResources)			
	{
		LinkedListIterator<SoundResource *> itr 
			= LinkedListIterator<SoundResource *> (g_soundResources);
		itr.Head();
		SoundResource * sr;

		while((sr=itr.ReadAndNext()) != NULL)
		{
			SPEW((GROUP_DIRECTSOUND, "Client Application leaked sound resource: %s", sr->m_FileName));
			delete sr;
		}

		delete g_soundResources;
		g_soundResources = NULL;
	}

	if( SoundRenderer.m_Mixer )
	{
		delete SoundRenderer.m_Mixer;
		SoundRenderer.m_Mixer = NULL;
		DeleteCriticalSection(&g_soundCritSec);
	}

	SoundRenderer.m_isInitialized = false;

	if (SoundRenderer.m_workBuffer)
	{
		gos_Free(SoundRenderer.m_workBuffer);
		SoundRenderer.m_workBuffer = 0;
	}

	SPEW(( GROUP_DIRECTSOUND, "SoundRendererUninstall() - Finished" ));
	MusicManagerUninstall();

	gos_DestroyMemoryHeap(SoundHeap);
	SoundHeap = 0;
}


extern void gosSetThreadName(DWORD dwThreadID, char *szThreadName);

void SoundRendererCreateTimer()
{
//
// Init the sound thread the first time it's used.
//
	SoundRenderer.m_TimerRefCount++;
	if( SoundRenderer.m_TimerRefCount!=1 ) 
		return;
//
// Create a thread to check streaming audio
//
	DWORD ID;
	KillSoundEvent=CreateEvent( NULL, true, false, NULL );
	SoundTimerThread=CreateThread(NULL, 0, SoundRendererUpdate, 0, 0, &ID );
	gosSetThreadName( ID, "Sound" );
}



void SoundRendererDestroyTimer()
{
	// don't destroy the timer if it still has references to it

	SoundRenderer.m_TimerRefCount--;

	if( SoundRenderer.m_TimerRefCount==0 )
	{
        SetEvent( KillSoundEvent );
        WaitForSingleObject( SoundTimerThread, INFINITE );
		CloseHandle( SoundTimerThread );
		CloseHandle( KillSoundEvent );
		SoundTimerThread=0;
	}
}


//
// Sound thread - called 10 times per second.
//
DWORD WINAPI SoundRendererUpdate( LPVOID ThreadParam )
{

    while( WaitForSingleObject( KillSoundEvent, 50 ) == WAIT_TIMEOUT )
	{

		if (!SoundRenderer.m_isInitialized)
		{
			continue;
		}
			//
// Loop through the channels looking for streaming files that are currently playing.
//
		EnterCriticalSection( &g_soundCritSec );

		#if LAB_ONLY
		__int64 tmpTime = GetCycles();
		#endif

		for(int i = 0; i < SoundRenderer.m_numChannels; i++)
		{
			if ( SoundRenderer.m_Channel[i] && SoundRenderer.m_Channel[i]->m_Buffer && 
				( (SoundRenderer.m_Channel[i]->m_resourceType == gosAudio_UserMemoryPlayList || 
				SoundRenderer.m_Channel[i]->m_resourceType == gosAudio_StreamedFile || 
				SoundRenderer.m_Channel[i]->m_resourceType == gosAudio_StreamedFP)) )
			{
				if ( SoundRenderer.m_Channel[i]->m_fPlaying && !SoundRenderer.m_Channel[i]->m_fPaused )
				{
					SoundRenderer.m_Channel[i]->ServicePlayList();
				}
			}
		}

		#if LAB_ONLY
		TimeAudio = (GetCycles() - tmpTime)/(ProcessorSpeed*1000);
		#endif

		LeaveCriticalSection( &g_soundCritSec );
	}
//
// Close down thread
//
	return 0;
}





void SoundRendererPause()
{
	SPEW((GROUP_DIRECTSOUND, "SoundRendererPause()" ));
	if (Environment.soundDisable == true || SoundRenderer.m_isInitialized == false) return;
	for(int i = 0; i < SoundRenderer.m_numChannels; i++)
	{
		if ( SoundRenderer.m_Channel[i] )
		{
			if ( SoundRenderer.m_Channel[i]->m_Buffer && SoundRenderer.m_Channel[i]->IsPlaying())
			{
				SPEW((GROUP_DIRECTSOUND, "..Pausing Channel#%d", i ));
				SoundRenderer.m_Channel[i]->m_sysPause = true;
				wStop(SoundRenderer.m_Channel[i]->m_Buffer);
			}
			else
			if (SoundRenderer.m_Channel[i]->m_resourceType == gosAudio_StreamedFile && SoundRenderer.m_Channel[i]->IsPlaying())
			{
				SoundRenderer.m_Channel[i]->m_sysPause = true;
				gosMusic_SetPlayMode(SoundRenderer.m_Channel[i]->m_currentSoundResource->m_hMusic, gosMusic_Pause);
			}
		}
	}

	SoundRenderer.m_allPaused = true;
}

void SoundRendererContinue()
{
	SPEW((GROUP_DIRECTSOUND, "SoundRendererContinue()" ));
	if (Environment.soundDisable == true || SoundRenderer.m_isInitialized == false || DisableSoundSystem ) return;
	for(int i = 0; i < SoundRenderer.m_numChannels; i++)
	{
		if ( SoundRenderer.m_Channel[i]->m_Buffer && SoundRenderer.m_Channel[i]->m_sysPause)
		{
			SPEW((GROUP_DIRECTSOUND, "..Continuing Channel#%d", i ));
			SoundRenderer.m_Channel[i]->m_sysPause = false;
			SafeFPU();					// Make sure exceptions cannot be generated
			wPlay(SoundRenderer.m_Channel[i]->m_Buffer, 0,0, SoundRenderer.m_Channel[i]->m_bLoopFlag ? DSBPLAY_LOOPING : 0 );
			GameOSFPU();				// Restore current GameOS FPU control word setup
		}
		else
		if (SoundRenderer.m_Channel[i]->m_resourceType == gosAudio_StreamedFile && SoundRenderer.m_Channel[i]->m_sysPause )
		{
			SoundRenderer.m_Channel[i]->m_sysPause = false;
			gosMusic_SetPlayMode(SoundRenderer.m_Channel[i]->m_currentSoundResource->m_hMusic, gosMusic_Continue);
		}
	}
	SoundRenderer.m_allPaused = false;
}

void __stdcall gosAudio_Reset()
{
	#ifdef LAB_ONLY
	char tmp[256];		   
	sprintf(tmp, "%9.2f    Reset               n/a\n",  gos_GetElapsedTime());
	strncpy( SoundRenderer.m_playHistory[SoundRenderer.m_playHistoryItr], tmp, 127 );
	SoundRenderer.m_playHistoryItr++;
	if (SoundRenderer.m_playHistoryItr == 32)
		SoundRenderer.m_playHistoryItr = 0;
	#endif

	SPEW((GROUP_DIRECTSOUND, "Resetting Sound System...." ));

	EnterCriticalSection(&g_soundCritSec);
	SoundRenderer.m_reset = true;
	if (SoundRenderer.m_Mixer)
	{
		SoundRenderer.m_Mixer->Reset();
	}
	else
	{
		SoundRenderer.m_Mixer = new(SoundHeap) DS3DSoundMixer();
	}
	SoundRenderer.m_reset = false;
	LeaveCriticalSection(&g_soundCritSec);
}

void __stdcall gosAudio_DeviceOptions(char names[8][128], int * available)
{
	for(int i=0; i < 8; i++)
	{
		if (i < SoundRenderer.m_numDevices)
			strncpy(names[i], SoundRenderer.m_DeviceArray[i].Description, 127);
		else
			names[i][0] = 0;

	}
	*available = SoundRenderer.m_numDevices;
}



//
// Sound device enum
//
BOOL CALLBACK DSEnumProc( LPGUID lpGUID, LPCTSTR lpszDesc, LPCTSTR lpszDrvName, LPVOID lpContext )
{
	if( SoundRenderer.m_numDevices==0 )
	{
		SoundRenderer.m_numDevices++;
		return true;				// Skip 'Primary device'
	}
	if( SoundRenderer.m_numDevices==8 )
		return false;				// Maximum of 8 devices
//
// Get device details
//
	memset( &SoundRenderer.m_DeviceArray[SoundRenderer.m_numDevices-1], 0, sizeof(SoundRenderer.m_DeviceArray[SoundRenderer.m_numDevices]) );
	SoundRenderer.m_DeviceArray[SoundRenderer.m_numDevices-1].Guid=*lpGUID;
	strncpy( SoundRenderer.m_DeviceArray[SoundRenderer.m_numDevices-1].Description, lpszDesc, 127 );
	strncpy( SoundRenderer.m_DeviceArray[SoundRenderer.m_numDevices-1].Driver, lpszDrvName, 127 );
	strlwr( SoundRenderer.m_DeviceArray[SoundRenderer.m_numDevices-1].Driver );
	FixedLengthString Buffer(1024);
	GetDriverFileDetails( Buffer, SoundRenderer.m_DeviceArray[SoundRenderer.m_numDevices-1].Driver, 1 );
	strncpy(SoundRenderer.m_DeviceArray[SoundRenderer.m_numDevices-1].Version, Buffer,255);
//
// Create a device and get the caps
//
	IDirectSound* pDS;
	AllowFail=1;
	HRESULT hr=wDirectSoundCreate( lpGUID, &pDS, 0 );
	AllowFail=0;
	if( SUCCEEDED(hr) )
	{
		SoundRenderer.m_DeviceArray[SoundRenderer.m_numDevices-1].Caps.dwSize=sizeof(SoundRenderer.m_DeviceArray[SoundRenderer.m_numDevices-1].Caps);
		AllowFail=1;
		wGetCaps( pDS, &SoundRenderer.m_DeviceArray[SoundRenderer.m_numDevices-1].Caps );
		AllowFail=0;

		AllowFail=1;
		wRelease( pDS );
		AllowFail=0;

		if (Environment.soundDevice == SoundRenderer.m_numDevices-1)
		{
			SoundRenderer.m_PreferredDevice = SoundRenderer.m_numDevices-1;
		}
	}
	SoundRenderer.m_numDevices++;
	return true;
}




void SoundRendererFF(double sec)
{
	// loop through the channels looking for streaming files that are currently playing.
	if (!SoundRenderer.m_isInitialized)
		return;
	EnterCriticalSection( &g_soundCritSec );

	for(int i = 0; i < SoundRenderer.m_numChannels; i++)
	{
		if ( SoundRenderer.m_Channel[i]->m_Buffer )
		{
			if ( SoundRenderer.m_Channel[i]->m_resourceType == gosAudio_UserMemoryPlayList)
			{
				if ( SoundRenderer.m_Channel[i]->m_fPlaying && !SoundRenderer.m_Channel[i]->m_fPaused )
				{
					// do the fast forward
					WAVEFORMATEX waveFormat;
					memset(&waveFormat, 0, sizeof(waveFormat));
					wGetFormat(SoundRenderer.m_Channel[i]->m_Buffer, &waveFormat, sizeof(waveFormat), NULL);
					DWORD bytes = (DWORD) (sec*waveFormat.nAvgBytesPerSec);
					DWORD finalPos;

					if (SoundRenderer.m_Channel[i]->m_cbBufOffset + bytes > SoundRenderer.m_Channel[i]->m_cbBufSize)
						finalPos = (SoundRenderer.m_Channel[i]->m_cbBufOffset + bytes) - SoundRenderer.m_Channel[i]->m_cbBufSize;
					else
						finalPos = bytes+SoundRenderer.m_Channel[i]->m_cbBufOffset;
					wSetCurrentPosition(SoundRenderer.m_Channel[i]->m_Buffer, finalPos);
					if (SoundRenderer.m_Channel[i]->m_resourceType == gosAudio_UserMemoryPlayList)
						SoundRenderer.m_Channel[i]->ServicePlayList();

					if (!gFreezeLogic)
					{
						SafeFPU();					// Make sure exceptions cannot be generated
						wPlay(SoundRenderer.m_Channel[i]->m_Buffer, 0,0, SoundRenderer.m_Channel[i]->m_bLoopFlag ? DSBPLAY_LOOPING : 0 );
						GameOSFPU();				// Restore current GameOS FPU control word setup
					}
				}
			}
			else
			{
				if ( SoundRenderer.m_Channel[i]->m_fPlaying && !SoundRenderer.m_Channel[i]->m_fPaused ) 
				{
					// do the fast forward
					WAVEFORMATEX waveFormat;
					memset(&waveFormat, 0, sizeof(waveFormat));
					wGetFormat(SoundRenderer.m_Channel[i]->m_Buffer, &waveFormat, sizeof(waveFormat), NULL);
					DWORD bytes = (DWORD) (sec*waveFormat.nAvgBytesPerSec);
					DWORD where;

					wGetCurrentPosition(SoundRenderer.m_Channel[i]->m_Buffer, &where, NULL);
					where += bytes;
					while (where >= SoundRenderer.m_Channel[i]->m_dsbCaps.dwBufferBytes)
						where = where - SoundRenderer.m_Channel[i]->m_dsbCaps.dwBufferBytes;
					if (SoundRenderer.m_Channel[i]->m_bLoopFlag)
						wSetCurrentPosition(SoundRenderer.m_Channel[i]->m_Buffer, where);
					else
						SoundRenderer.m_Channel[i]->Stop();
				}
			}
		}
		else
		if (SoundRenderer.m_Channel[i]->m_resourceType == gosAudio_StreamedFile )
		{
			if ( SoundRenderer.m_Channel[i]->IsPlaying() && !SoundRenderer.m_Channel[i]->m_fPaused )
			{
				SoundRenderer.m_Channel[i]->m_currentSoundResource->m_hMusic->FF(sec);
			}
		}
	}

	LeaveCriticalSection( &g_soundCritSec );
}


void SoundRendererNotify()
{
	// don't do anything at the moment...
}

bool IsValidSoundResource(HGOSAUDIO resource)
{
	LinkedListIterator<SoundResource *> itr 
		= LinkedListIterator<SoundResource *> (g_soundResources);
	itr.Head();
	SoundResource * sr;

	while((sr=itr.ReadAndNext()) != NULL)
	{
	   if (sr == (SoundResource *) resource)
	   {
		   return true;
	   }
	}
	return false;
}

//
// This stuff doesn't appear in the GameOS.hpp, but you can extern to it.... for our friends in spike...
//
void* __stdcall gosAudio_GetDXSoundPtr()
{
    if( SoundRenderer.m_Mixer )
    {
        wAddRef( SoundRenderer.m_Mixer->m_lpDirectSound );
        return SoundRenderer.m_Mixer->m_lpDirectSound;
    }

    return NULL;
}

void* __stdcall gosAudio_GetDXPrimaryBuffer()
{
    if( SoundRenderer.m_Mixer )
    {
        wAddRef( SoundRenderer.m_Mixer->m_lpPrimaryBuffer );
        return SoundRenderer.m_Mixer->m_lpPrimaryBuffer;
    }
    return NULL;
}
