#include "pch.hpp"
//===========================================================================//
// File:	 Music.cpp												 //
// Project:  GameOS					                                         //
// Contents: Video Playback routines										 //
//---------------------------------------------------------------------------//
// Date      Who    Modification                                             //
// 05/05/99  JTR    Created													 //
//																			 //
//---------------------------------------------------------------------------//
// Copyright (C) 1999 Microsoft Corp.						                 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//
#include "Globals.hpp"
#include "LinkedList.hpp"
#include "DirectX.hpp"
#include "Music.hpp"
#include "Sound Renderer.hpp"
#include "Sound DS3DMixer.hpp"
#include "ErrorHandler.hpp"

#define GROUP_DIRECTSHOW "GameOS_DirectShow"

// for memory tracking
extern HGOSHEAP SoundHeap;

extern SoundRendererData SoundRenderer;
bool gos_Music::m_hasDirectShow = false;

extern HWND g_MovieHandle;
extern HGOSMUSIC g_activeMusicList;
extern DWORD gStopRendering;
extern bool DebuggerActive;
extern DWORD gFreezeLogic;
extern DWORD gGameLogicActive;
extern DWORD gStopSystem;
extern HWND hWindow;

extern bool gGotFocus;
extern bool gIsQuiet;

void MusicManagerInstall()
{
	SPEW(( GROUP_DIRECTSHOW, "MusicManagerInstall()" ));
	///////////////////////////////
	OSVERSIONINFO osinfo;
	osinfo.dwOSVersionInfoSize=sizeof(osinfo);
	GetVersionEx( &osinfo );

	gos_Music::m_hasDirectShow = true;
	if( osinfo.dwPlatformId==VER_PLATFORM_WIN32_NT  && ((int)osinfo.dwMajorVersion) < 5)
	{
		SPEW(("Warning", "This operating system doesn't support GameOS music playback" ));
		gos_Music::m_hasDirectShow = false;
	}

	g_activeMusicList = NULL;
}




void MusicManagerRestore()
{
	SPEW(( GROUP_DIRECTSHOW, "MusicManagerRestore()" ));
	if (!gos_Music::m_hasDirectShow || Environment.soundDisable)
		return;
	gos_Music * 
		music = g_activeMusicList;
	while(music)
	{
		music->Restore();
		music = music->m_pNext;
	}
}

///////////////////////////////////////////////////////////////////
//
// The update function iterates through the list of movies and
// renders any that are active. If we are in 8bit mode, make
// sure that the palette is correct to show our movie.
//
void MusicManagerUpdate()
{
	if (!gos_Music::m_hasDirectShow || Environment.soundDisable)
		return;
	if (gFreezeLogic || (DebuggerActive && (!gGameLogicActive  || gStopSystem)) )
	{
		return;
	}

	HGOSMUSIC 
		music = g_activeMusicList;

	while(music)
	{
		if (music->m_musicStatus == gosMusic_PlayOnce || 
			music->m_musicStatus == gosMusic_Loop)
		{
			if (!gGotFocus || gIsQuiet || !gActive)
			{
				if (SoundRenderer.m_Mixer)
				{
					if (music->m_pBasicAudio)
					{
						AllowFail = true;
						wPut_Volume( music->m_pBasicAudio, -10000 );
						AllowFail = false;
					}
				}
			}
			else
			if (gActive && gGotFocus && !gIsQuiet)
			{
				if (SoundRenderer.m_Mixer)
				{
					if (music->m_pBasicAudio)
					{
						if (music->m_fVolume == 0.0f)
						{
							AllowFail = true;
							wPut_Volume( music->m_pBasicAudio, -10000 );
							AllowFail = false;
						}
						else
						{
							AllowFail = true;
							wPut_Volume( music->m_pBasicAudio, (LONG) (log(music->m_fVolume)*1000.0f ) );
							AllowFail = false;
						}
					}
				}
			}
			music->Update();
		}
	 	music = music->m_pNext;
	}
}

///////////////////////////////////////////////////////////////////
//
// Music Manager shutdown iterates through the list of allocated
// music and deletes them. There really shouldn't be any by the
// time this gets called-- we should probably inform the runtime
// log if any still exist
//
void 
	MusicManagerUninstall()
{
	SPEW(( GROUP_DIRECTSHOW, "MusicManagerUninstall()" ));

	HGOSMUSIC
		tmp,
		music =  g_activeMusicList;

	while(music)
	{
		tmp = music->m_pNext;
		delete music;
		music = tmp;
	}

	g_activeMusicList = NULL;
}

///////////////////////////////////////////////////////////////////
//
// A wrapper for creating a Video resource. It requires a pointer
// to a music handle, a path, and a destination surface.
//
void __stdcall gosMusic_CreateResource(HGOSMUSIC* music, char * path)
{
	*music = (HGOSMUSIC) new(SoundHeap) gos_Music(path);
	((gos_Music *) *music)->m_pNext = g_activeMusicList;
	g_activeMusicList = *music;
}

///////////////////////////////////////////////////////////////////
//
// Destroy a music (the destructor removes the music from the list)
//
void __stdcall gosMusic_DestroyResource(HGOSMUSIC* music)
{
	gosASSERT(*music != NULL);

	HGOSMUSIC 
		tmp = g_activeMusicList,
		m = g_activeMusicList;

	while(m)
	{
		if (*music == m)
		{
			if (*music == g_activeMusicList)
			{
				g_activeMusicList = g_activeMusicList->m_pNext;
				delete *music;
				*music = NULL;
				return;
			}
			else
			{
				tmp->m_pNext = m->m_pNext;
				delete *music;
				*music = NULL;
				return;
			}
		}
		tmp = m;
		m = m->m_pNext;
	}
#ifdef LAB_ONLY
	STOP(( "gosMusic_DestroyResource was passed a bad HGOSMUSIC"));
#endif
}

void __stdcall gosMusic_GetResourceInfo( HGOSMUSIC music, gosMusic_ResourceInfo * gvri)
{
	memset(gvri,0,sizeof(gosMusic_ResourceInfo));

	if (!gos_Music::m_hasDirectShow || Environment.soundDisable)
	{
		gvri->fDurationSec = 0.0;	
		gvri->fSoFarSec = 0.0;
		return;
	}

	// plug in the data
	gvri->ePlayMode = music->m_musicPlayMode;
	gvri->ePlayStatus = music->m_musicStatus;

	gvri->lpstrPath = music->m_lpPath;
	_int64 st;
	gvri->fDurationSec = (float) ( music->m_fDurationSec / 10000000.0);	

	wGetTime( music->m_pMMStream, &st );
	
	gvri->fSoFarSec = (float) (st / 10000000.0);
	gvri->fVolume = music->m_fVolume;
	gvri->fPanning = music->m_fPanning;
}

///////////////////////////////////////////////////////////////////
//
// Return the play status of the specified music. Find out if its
// PLAYING, STOPPED, PAUSED.
//
gosMusic_PlayMode __stdcall gosMusic_GetPlayMode(HGOSMUSIC music)
{
	gosASSERT(music);
	if (!gos_Music::m_hasDirectShow || Environment.soundDisable)
		return gosMusic_Stop;
	return music->m_musicStatus;


	gosASSERT(music);
	if( 1==LogMode )	// Log file playing back?
	{
		DWORD retVal;
		retVal = ReadBitStream(4,Log_Media);
		return (gosMusic_PlayMode) retVal;
	}
	else
	{
		if ( !music->m_pMMStream || !gos_Music::m_hasDirectShow || Environment.soundDisable)
		{
			WriteBitStream(gosMusic_Stop,4,Log_Media);
			return gosMusic_Stop;
		}
		else
		{

			WriteBitStream(music->m_musicStatus,4,Log_Media);
			return music->m_musicStatus;
		}
	}

}


///////////////////////////////////////////////////////////////////
//
// Create the basic mm stream for a a/v stream.
//
void
	gos_Music::OpenMMStream()
{
    m_pMMStream = NULL;
    IAMMultiMediaStream *
		pAMStream = NULL;

    wCoCreateInstance(CLSID_AMMultiMediaStream, NULL, CLSCTX_INPROC_SERVER, IID_IAMMultiMediaStream, (void **)&pAMStream);
	if (pAMStream == NULL)
	{
		STOP(( "Could not play music. Is DirectShow installed?" ));
	}

    wInitialize(pAMStream, STREAMTYPE_READ, 0, NULL);
    wAddMediaStream(pAMStream, SoundRenderer.m_Mixer->m_lpDirectSound, &MSPID_PrimaryAudio, AMMSF_ADDDEFAULTRENDERER, NULL);

    WCHAR       wPath[MAX_PATH];
    MultiByteToWideChar(CP_ACP, 0, m_lpPath, -1, wPath, sizeof(wPath)/sizeof(wPath[0]));

	if (gos_DoesFileExist(m_lpPath) == FALSE)
	{
		char tmp[128];
		sprintf(tmp, "Couldn't find file %s", m_lpPath);
		STOP(( tmp ));
	}
    if (wOpenFile(pAMStream,wPath, 0))
	{
		wRelease(pAMStream);
		m_pMMStream = 0;
		m_pBasicAudio = 0;
		return;
	};

	IGraphBuilder * pGB;
	wGetFilterGraph( pAMStream, &pGB );
	wQueryInterface(pAMStream, IID_IMultiMediaStream, (void **) &m_pMMStream);

	IMediaControl * pMC = NULL;
    wQueryInterface(pGB,IID_IMediaControl, (void **)&pMC);
	AllowFail = true;
    wQueryInterface(pMC,IID_IBasicAudio, (void **)&m_pBasicAudio);
	AllowFail = false;

	wRelease(pMC);
	wRelease(pGB);

    if (m_pMMStream == NULL) 
	{
		STOP(( "Could not create a CLSID_MultiMediaStream object" ));
    }
    wRelease(pAMStream);
}

///////////////////////////////////////////////////////////////////
//
// Create an a/v stream and make sure it is synched
//
gos_Music::gos_Music( char * path)
{
	gosASSERT(path);

	m_pNext = NULL;
	
	m_pMMStream = NULL;
	m_pBasicAudio = NULL;

	m_musicStatus = gosMusic_Stop;
	m_lastKnownTime = 0L;
	m_fVolume = 1.0f;
	m_fPanning = 0.0f;
	m_fSoFarSec = 0;
	m_fDurationSec = 0;	
	m_lpPath = (char *) gos_Malloc(strlen(path)+1, SoundHeap);
	strcpy(m_lpPath, path);

	if (!m_hasDirectShow || Environment.soundDisable)
		return;

	OpenMMStream();

	wGetDuration(m_pMMStream,&m_fDurationSec);
	wGetEndOfStreamEventHandle(m_pMMStream, &phEOS);
}

///////////////////////////////////////////////////////////////////
//
// Kill the data relating to a particular videostream and remove
// it from the list of videostreams.
//
gos_Music::~gos_Music()
{
	if (m_lpPath)
	{
		gos_Free(m_lpPath);
		m_lpPath = 0;
	}
	if (m_musicStatus != gosMusic_Stop) 
		Stop();
	if (m_pMMStream) 
	{
		wRelease(m_pMMStream);
		m_pMMStream = 0;
	}
	if (m_pBasicAudio) 
	{
		wRelease(m_pBasicAudio);
		m_pBasicAudio = 0;
	}
}

///////////////////////////////////////////////////////////////////
//
// If a music is playing, update the stream. If the music is done
// playing and the looping flag is set, wrap the stream around to
// the beginning. If not set the status flag to STOPPED.
//
bool
	gos_Music::Update()
{
	// if paused, keep display the current frame/sound
	if (!m_hasDirectShow || Environment.soundDisable || !m_pMMStream)
		return FALSE;
	if (m_musicStatus == gosMusic_Pause || 
		(m_musicStatus == gosMusic_Stop && m_musicPlayMode == gosMusic_PlayOnce ) )
	{
		return TRUE; 
	}

	wGetTime(m_pMMStream, &m_lastKnownTime);
	if ( m_lastKnownTime >= m_fDurationSec)
	{
		if ( WaitForSingleObject(phEOS,0) == WAIT_OBJECT_0 )
		{
			if (m_musicPlayMode == gosMusic_PlayOnce)
			{
				Stop();
				return FALSE;
			}
			else
			if (m_musicPlayMode == gosMusic_Loop)
			{
				wSeek(m_pMMStream, 0); 
				wSetState(m_pMMStream,STREAMSTATE_RUN);
				wGetTime(m_pMMStream, &m_fSoFarSec);
			}
		}
		else
			return TRUE;
	}
	m_fSoFarSec = m_lastKnownTime;

	return TRUE;
}

///////////////////////////////////////////////////////////////////
//
// Stop the music at its current location. The music can be 
// restarted by calling "play" again.
//
void
	gos_Music::Stop()
{
	if (!m_hasDirectShow || Environment.soundDisable || !m_pMMStream)
		return;
    m_musicStatus = gosMusic_Stop;
	wSetState(m_pMMStream,STREAMSTATE_STOP);
}

///////////////////////////////////////////////////////////////////
//
// Pause the music at its current location.
//
void
	gos_Music::Pause()
{
	if (!m_hasDirectShow || Environment.soundDisable || !m_pMMStream)
		return;
	if (m_musicStatus == gosMusic_Pause)
		return;
	gosASSERT(m_musicStatus == gosMusic_Loop || m_musicStatus == gosMusic_PlayOnce || m_musicStatus == gosMusic_Pause);

	wSetState(m_pMMStream,STREAMSTATE_STOP);
    m_musicStatus = gosMusic_Pause;
}

///////////////////////////////////////////////////////////////////
//
// Set the music to running mode beginning at where we left off
// when we paused.
//
void
	gos_Music::Continue()
{
	if (!m_hasDirectShow || Environment.soundDisable || !m_pMMStream)
		return;
	if (m_musicStatus != gosMusic_Pause)
		return;
	wSetState(m_pMMStream,STREAMSTATE_RUN);
    m_musicStatus = m_musicPlayMode;
}

void __stdcall gosMusic_Command( HGOSMUSIC music, enum gosMusic_Command vc, float x, float y)
{
	if (!gos_Music::m_hasDirectShow || Environment.soundDisable || !music->m_pMMStream)
		return;
#ifdef LAB_ONLY
	HGOSMUSIC 
		m = g_activeMusicList;
	bool found = false;
	while(m)
	{
		if (m == music)
		{
			found = true;
			break;
		}
		m = m->m_pNext;
	}
	if (!found)
		STOP(( "gosMusic_DestroyResource was passed a bad HGOSMUSIC"));
#endif

	switch(	vc )
	{
	case gosMusic_SeekTime: 		// Seek to a frame or timestamp in <hVideo>
		{
#ifdef LAB_ONLY
			if (x > (float) music->m_fDurationSec)
				STOP(( "You've attempted to Seek past the end of a music" ));
#endif
			wSeek(music->m_pMMStream,(STREAM_TIME)x*10000000);
			break;
		};
	case gosMusic_Volume:
		{
			if (x < 0) x = 0.0f;
			if (x > 1.0) x = 1.0f;
			music->m_fVolume = x;
			AllowFail = true;
			x *= SoundRenderer.m_Mixer->m_fVolume;
			if (music->m_pBasicAudio)
			{
				if (x == 0.0f)
					wPut_Volume( music->m_pBasicAudio, -10000 );
				else
					wPut_Volume( music->m_pBasicAudio, (LONG) (log(x)*1000.0f ) );
			}
			AllowFail = false;
			break;
		}
	case gosMusic_Panning:
		{
			if (x < -1.0) x = -1.0f;
			if (x > 1.0) x = 1.0f;
			music->m_fPanning = x;

			AllowFail = true;
			if (music->m_pBasicAudio)
			{
					if (x > 0.0f)
					{
						if (x >= .99f)
							wPut_Balance( music->m_pBasicAudio, 10000 );
						else
							wPut_Balance( music->m_pBasicAudio, (LONG)((log(1.0f-x)*-1000.0f)) );
					}
					else
					if (x < 0.0f)
					{
						if (x <= -.99f)
							wPut_Balance( music->m_pBasicAudio, -10000 );
						else
							wPut_Balance( music->m_pBasicAudio, (LONG)((log(1.0f+x)*1000.0f)) );
					}
					else
						wPut_Balance( music->m_pBasicAudio, 0 );
			}
			AllowFail = false;
			break;
		}
	}
}

void __stdcall gosMusic_SetPlayMode(HGOSMUSIC music, enum gosMusic_PlayMode gvpm)
{
	if (!gos_Music::m_hasDirectShow || Environment.soundDisable || !music->m_pMMStream)
		return;
#ifdef LAB_ONLY
	HGOSMUSIC 
		m = g_activeMusicList;
	bool found = false;
	while(m)
	{
		if (m == music)
		{
			found = true;
			break;
		}
		m = m->m_pNext;
	}
	if (!found)
		STOP(( "gosMusic_SetPlayMode was passed a bad HGOSMUSIC"));
#endif

	switch(gvpm)
	{
	case gosMusic_PlayOnce:			// The music is currently playing (will end when done)
	case gosMusic_Loop:				// The music is currently in continuous play mode
		{
			music->m_musicPlayMode = gvpm;
			music->m_musicStatus = gvpm;

			// go to the start of the movie
			wSeek(music->m_pMMStream,0);
		    wSetState(music->m_pMMStream, STREAMSTATE_RUN);
			music->m_fSoFarSec = 0;
			break;
		}
	case gosMusic_Stop:				// The music is stopped
		{
			music->Stop();
			music->m_fSoFarSec = 0;
			break;
		}
	case gosMusic_Pause:				// The music has been paused
		{
			music->Pause();
			break;
		}
	case gosMusic_Continue:			// SET ONLY: continue a paused music
		{
			music->Continue();
			break;
		}
	}
}


void gos_Music::Restore()
{
	if (!m_hasDirectShow || Environment.soundDisable || !m_pMMStream)
		return;
	if (m_pMMStream) wRelease(m_pMMStream);
	if (m_pBasicAudio) wRelease(m_pBasicAudio);

	OpenMMStream();

	gosMusic_Command(this, gosMusic_Volume, m_fVolume);
	gosMusic_Command(this, gosMusic_Panning, m_fPanning);
	wSeek( m_pMMStream,m_lastKnownTime);
    gosMusic_SetPlayMode(this, gosMusic_Pause);
    gosMusic_SetPlayMode(this, gosMusic_Continue);
}


void gos_Music::FF(double time)
{
	//
	// To FF the movie, grab the current time off the stream and then
	// add a seek based on the time we've been past.
	//
	DWORD result;
	STREAM_TYPE st;
	wGetInformation(m_pMMStream,&result, &st);

	if (result & MMSSF_SUPPORTSEEK )
	{
		STREAM_TIME     current;
		wGetTime(m_pMMStream, &current);
		current += (_int64) (time *10000000.0f);
		current %= m_fDurationSec;
		wSeek(m_pMMStream, current); 
	}
}


