/* 
  UMSynth.cpp : Implementation of CUserModeSynth

  This wraps the CSynth synthesizer module with the IDirectMusicSynth interface. 
*/
//      Copyright (c) 1996-1999 Microsoft Corporation

// READ THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// 4530: C++ exception handler used, but unwind semantics are not enabled. Specify -GX
//
// We disable this because we use exceptions and do *not* specify -GX (USE_NATIVE_EH in
// sources).
//
// The one place we use exceptions is around construction of objects that call 
// InitializeCriticalSection. We guarantee that it is safe to use in this case with
// the restriction given by not using -GX (automatic objects in the call chain between
// throw and handler are not destructed). Turning on -GX buys us nothing but +10% to code
// size because of the unwind code.
//
// Any other use of exceptions must follow these restrictions or -GX must be turned on.
//
// READ THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
#pragma warning(disable:4530)


#include <objbase.h>
#include <ks.h>

#include "debug.h"
#include "UMSynth.h"
#include "dmusicc.h"
#include "dmusics.h"
#include "math.h"
#include "misc.h"
#include "dmksctrl.h"

#include <dmusprop.h>

#include "validate.h"

// Component count is used to keep track of whether this module needs to stay loaded by COM.
// It gets incremented with each COM object (synth) created and decremented with each on 
// deleted. 
extern long g_cComponent; 

/////////////////////////////////////////////////////////////////////////////
// CUserModeSynth
//

// Init
// This function is called to initialize any parameters at object creation time.
// Currently, we don't have any need to call this.

HRESULT CUserModeSynth::Init()
{
	return S_OK;
}


/* CUserModeSynth
   On creation of the synth, initialize the critical section and various global parameters.
   This is called by the C++ constructor, as part of the new operation that creates this
   (which is called from the class factory.)
*/

CUserModeSynth::CUserModeSynth()

{
	InterlockedIncrement(&g_cComponent);

    m_fCSInitialized = FALSE;
    ::InitializeCriticalSection(&m_CriticalSection);
    m_fCSInitialized = TRUE;

	m_cRef = 0;
	m_dwSampleRate = 22050;
	m_dwChannels = 2;
    m_lVolume = 0;
    m_lBoost = 6 * 100;
    m_lGainAdjust = 6 * 100;            // Default 6 dB boost
	m_fActive = FALSE;
    m_pSynth = NULL;
	m_pSynthSink = NULL;

}

// ~CUserModeSynth
// When the synth is destroyed, be sure to clean up...

CUserModeSynth::~CUserModeSynth()

{
	Activate(FALSE);

    if (m_fCSInitialized)
    {
        ::EnterCriticalSection(&m_CriticalSection);
    	if (m_pSynth)
    	{
            delete m_pSynth;
    		m_pSynth = NULL;
    	}
    	if (m_pSynthSink)
    	{
    		m_pSynthSink->Release();
    	}
    	::LeaveCriticalSection(&m_CriticalSection);
        ::DeleteCriticalSection(&m_CriticalSection);
    }

  	InterlockedDecrement(&g_cComponent);
}


/* QueryInterface
   Standard COM stuff. The synth is represented by the
   IUnknown, IDirectMusicSynth, and IKsControl interfaces.
*/

STDMETHODIMP
CUserModeSynth::QueryInterface(const IID &iid,
                                   void **ppv)
{
    V_INAME(IDirectMusicSynth::QueryInterface);
    V_REFGUID(iid);
    V_PTRPTR_WRITE(ppv);

    if (iid == IID_IUnknown || iid == IID_IDirectMusicSynth) {
        *ppv = static_cast<IDirectMusicSynth*>(this);
    }
	else if (iid == IID_IKsControl) 
    {
        *ppv = static_cast<IKsControl*>(this);
    }
    else
	{
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    
    reinterpret_cast<IUnknown*>(this)->AddRef();
    return S_OK;
}


// CUserModeSynth::AddRef
//
STDMETHODIMP_(ULONG)
CUserModeSynth::AddRef()
{
    return InterlockedIncrement(&m_cRef);
}

// CUserModeSynth::Release
//
STDMETHODIMP_(ULONG)
CUserModeSynth::Release()
{
    if (!InterlockedDecrement(&m_cRef)) {
        delete this;
        return 0;
    }

    return m_cRef;
}

/* SetSynthSink
   This is called by DirectMusic when it is in the process of connecting a 
   synthesizer to a synth sink. Although the synthsink calls the synth's
   Render method to generate data, the synth also needs access to the sink
   so it can call its methods, in particular RefTimeToSample, which is used
   to translate the reference time of an incoming MIDI message into a sample
   accurate position in the audio buffer.
   The IDirectMusicSynthSink object does the work of actually connecting
   up to the ultimate audio destination, which might be Wave audio, 
   DirectSound, or some other audio stream. The default implementation
   sends to DirectSound. 
   This approach allows a synthesizer to connect to many different
   styles of audio out without special code within the synth itself.
*/

STDMETHODIMP CUserModeSynth::SetSynthSink(
	IDirectMusicSynthSink *pSynthSink)	// IDirectMusicSynthSink to connect to synth, or
										// NULL to disconnect.
{
	HRESULT hr = S_OK;
	V_INAME(IDirectMusicSynth::SetSynthSink);
	V_INTERFACE_OPT(pSynthSink);
	
    ::EnterCriticalSection(&m_CriticalSection);
    // If already connected to a sink, disconnect.
	if (m_pSynthSink)
	{
		hr = m_pSynthSink->Init(NULL);
		m_pSynthSink->Release();
	}

	m_pSynthSink = pSynthSink;

	if (m_pSynthSink)
	{
		m_pSynthSink->AddRef();
		hr = m_pSynthSink->Init(static_cast<IDirectMusicSynth*>(this));
	}
	::LeaveCriticalSection(&m_CriticalSection);
	
    return hr;
}

/* Open
   This is called when the synthesizer is first opened via a call to 
   IDirectMusicPort::CreatePort().
   CreatePort() first calls CoCreateInstance() to initialize the synth
   COM object, as specified by the REFCLSID parameter (NULL for default port.)
   Once the synth is initialized, IDirectMusicSynth::Open is called with 
   the DMUS_PORTPARAMS structure that was passed to CreatePort(). 
   It merges the requested values with its own internal default values, 
   creates the port, and returns the DMUS_PORTPARAMS altered to indicate the
   settings that it used to initialize the port. 
   The port is valid until it is closed with a call to
   IDirectMusicSynth::Close().
*/

STDMETHODIMP CUserModeSynth::Open(	
								  LPDMUS_PORTPARAMS pPortParams)
                                  // DMUS_PORTPARAMS structure for opening the port. If NULL, default settings are used.
{
	V_INAME(IDirectMusicSynth::Open);

	bool bPartialOpen = false; // Used to keep track of whether a param had to change.
	
	DMUS_PORTPARAMS myParams;       // Initial default settings. 
	myParams.dwSize = sizeof (myParams);
	myParams.dwVoices = 32;         // Default to 32 voices.
	myParams.dwChannelGroups = 2;   // Default to 32 channels.
	myParams.dwAudioChannels = 2;   // Default to stereo.
	myParams.dwSampleRate = 22050;  // Default to 22k sample rate.
#ifdef REVERB_ENABLED               // Default to reverb on, no chorus.
	myParams.dwEffectFlags = DMUS_EFFECT_REVERB;
#else
	myParams.dwEffectFlags = DMUS_EFFECT_NONE;
#endif
    myParams.fShare = FALSE;        // Can not share port with another app.
	myParams.dwValidParams =        // Indicate which fields have valid data.
		DMUS_PORTPARAMS_VOICES | 
		DMUS_PORTPARAMS_CHANNELGROUPS |
		DMUS_PORTPARAMS_AUDIOCHANNELS |
		DMUS_PORTPARAMS_SAMPLERATE |
		DMUS_PORTPARAMS_EFFECTS |
		DMUS_PORTPARAMS_SHARE; 
	if (pPortParams)                // Did app provide PORTPARAMS of its own?
                                    // If so, override the default params with these.
	{
		V_STRUCTPTR_READ(pPortParams,DMUS_PORTPARAMS);
		if (pPortParams->dwValidParams & DMUS_PORTPARAMS_VOICES)
		{
			if (pPortParams->dwVoices)
			{
				if (pPortParams->dwVoices <= MAX_VOICES)
				{
					myParams.dwVoices = pPortParams->dwVoices;
				}
				else
				{
					bPartialOpen = true;    // Since this is a change from the requested value, we'll need to change the return code to S_FALSE.
					myParams.dwVoices = MAX_VOICES;
				}
			}
			else
			{
				bPartialOpen = true;
				myParams.dwVoices = 1; // Smallest we'll allow is one voice.
			}
		}
		if (pPortParams->dwValidParams & DMUS_PORTPARAMS_CHANNELGROUPS)
		{
			if (pPortParams->dwChannelGroups)
            {
                if (pPortParams->dwChannelGroups <= MAX_CHANNEL_GROUPS)
			    {
				    myParams.dwChannelGroups = pPortParams->dwChannelGroups;
			    }
			    else
			    {
				    bPartialOpen = true;
				    myParams.dwChannelGroups = MAX_CHANNEL_GROUPS;
			    }
            }
			else
			{
				bPartialOpen = true;
				myParams.dwChannelGroups = 1; // MIN_CHANNEL_GROUPS
			}
		}
		if (pPortParams->dwValidParams & DMUS_PORTPARAMS_AUDIOCHANNELS)
		{
			if (pPortParams->dwAudioChannels)
            {
                if (pPortParams->dwAudioChannels <= 2)
			    {
				    myParams.dwAudioChannels = pPortParams->dwAudioChannels;
			    }
			    else
			    {
				    bPartialOpen = true;
				    myParams.dwAudioChannels = 2; // MAX_AUDIO_CHANNELS
			    }
            }
			else
			{
				bPartialOpen = true;
				myParams.dwAudioChannels = 1; // MIN_AUDIO_CHANNELS
			}
		}
		if (pPortParams->dwValidParams & DMUS_PORTPARAMS_SAMPLERATE)
		{
			if (pPortParams->dwSampleRate > 30000)
			{
				if(pPortParams->dwSampleRate != 44100)
				{
					bPartialOpen = true;
				}
				
				myParams.dwSampleRate = 44100;
			}
			else if (pPortParams->dwSampleRate > 15000)
			{
				if(pPortParams->dwSampleRate != 22050)
				{
					bPartialOpen = true;
				}

				myParams.dwSampleRate = 22050;
			}
			else 
			{
				if(pPortParams->dwSampleRate != 11025)
				{
					bPartialOpen = true;
				}

				myParams.dwSampleRate = 11025;
			}
		}
		if (pPortParams->dwValidParams & DMUS_PORTPARAMS_EFFECTS)
		{
            if (pPortParams->dwEffectFlags & ~DMUS_EFFECT_REVERB)
            {
                bPartialOpen = true;
                pPortParams->dwEffectFlags &= DMUS_EFFECT_REVERB;
            }

#ifdef REVERB_ENABLED
			myParams.dwEffectFlags = pPortParams->dwEffectFlags;
#else
			myParams.dwEffectFlags = DMUS_EFFECT_NONE; 
			if (pPortParams->dwEffectFlags & DMUS_EFFECT_REVERB)
			{
				bPartialOpen = true;
			}
#endif
        }
        if (pPortParams->dwValidParams & DMUS_PORTPARAMS_SHARE)
        {
            if (pPortParams->fShare)
            {
                bPartialOpen = true;
            }
        }
	}

    // If they passed a DMUS_PORTPARAMS structure, update it with the merged settings.
	if (pPortParams) 
	{
		if (pPortParams->dwSize >= sizeof (DMUS_PORTPARAMS))
		{
			*pPortParams = myParams;
		}
		else
		{
			DWORD dwSize = pPortParams->dwSize;
			memcpy(pPortParams,&myParams,pPortParams->dwSize);
			pPortParams->dwSize = dwSize;
		}
	}

    m_dwSampleRate = myParams.dwSampleRate;
    m_dwChannels = myParams.dwAudioChannels;

	::EnterCriticalSection(&m_CriticalSection);
	HRESULT hr = DMUS_E_ALREADYOPEN; // 
    // Now, open the synth.
	if (!m_pSynth)
	{
        // We use exception handling here because it's the only way to handle a failure
        // in InitializeCriticalSection.
        try
        {
		    m_pSynth = new CSynth;
        }
        catch( ... )
        {
            m_pSynth = NULL;
        }

		if (!m_pSynth)
		{
			hr = E_OUTOFMEMORY;
		}
		else
		{
            // Creation of structure worked. Go ahead and set up the channel groups, 
            // initialize reverb, etc...
			hr = m_pSynth->Open(myParams.dwChannelGroups,
                myParams.dwVoices,
                (myParams.dwEffectFlags & DMUS_EFFECT_REVERB) ? TRUE : FALSE);
			if (SUCCEEDED(hr))
			{
                m_pSynth->SetGainAdjust(m_lGainAdjust);
                m_pSynth->Activate(m_dwSampleRate, m_dwChannels);
			}
			else
			{
				delete m_pSynth;
				m_pSynth = NULL;
			}
		}
	}
	::LeaveCriticalSection(&m_CriticalSection);
	
	if(SUCCEEDED(hr))
	{
		if(bPartialOpen)
		{
            // Synth successfully created, but some parameters changed from what
            // was requested in pPortParams.
			hr = S_FALSE;
		}
	}
	
	return hr;
}

/* SetNumChannelGroups
   The number of channel groups can change dynamically under application control.
   So, the synthesizer must be capable of changing this, even as it continues to 
   play notes.
   Even though  IDirectMusicSynth::Open() tells the port how many
   channel groups to create, the application may later wish to dynamically
   increase or reduce the number with a call to 
   IDirectMusicSynth::SetNumChannelGroups(). 
   Each channel group supports a set of sixteen MIDI channels. 
   For example, if dwChannelGroups is set to 3, the synthesizer
   creates 48 channels. 
   In this implementation, this is maintained as an array of CControlLogic objects,
   each dedicated to processing one channel group. The call to CSynth->SetNumChannelGroups
   causes it to calculate whether it needs to add or remove CControlLogic objects,
   which it manages with an array of pointers. The critical section ensures that no
   other processing occurs while it reorganizes the array.
*/

STDMETHODIMP CUserModeSynth::SetNumChannelGroups(
	DWORD dwGroups)		// Number of ChannelGroups requested.

{
	::EnterCriticalSection(&m_CriticalSection);
	HRESULT hr = DMUS_E_SYNTHNOTCONFIGURED;
	if (m_pSynth)
	{
		hr = m_pSynth->SetNumChannelGroups(dwGroups);
	}
	::LeaveCriticalSection(&m_CriticalSection);
	return hr;
}

/* Close
   The Close method is called when the port is finally shut down. 
   In this implementation, we delete the CSynth object.
   All instruments and waves downloaded to
   the port are released (though a well behaved
   application should have unloaded them all prior to calling
   IDirectMusicSynth::Close().)
*/

STDMETHODIMP CUserModeSynth::Close()

{
	::EnterCriticalSection(&m_CriticalSection);
	HRESULT hr = DMUS_E_ALREADYCLOSED;
	if (m_pSynth)
	{
		hr = m_pSynth->Close();
		delete m_pSynth;
		m_pSynth = NULL;
	}
	::LeaveCriticalSection(&m_CriticalSection);
	return hr;
}

/* Download
   Receives a download chunk, either an instrument or wave, and parses it.
   See CInstManager::Download() for the implementation.
*/

STDMETHODIMP CUserModeSynth::Download(
	LPHANDLE phDownload,	// Pointer to download handle, to be created by IDirectMusicSynth::Download and used later to unload the data.
	LPVOID pvData,			// Pointer to continuous memory segment with download data.
	LPBOOL pbFree)			// pbFree indicates whether the synthesizer wishes to keep the memory in pvData allocated.
{
	HRESULT hr = DMUS_E_SYNTHNOTCONFIGURED;
	V_INAME(IDirectMusicSynth::Download);
	V_PTR_WRITE(phDownload,HANDLE); 
	V_PTR_WRITE(pbFree,BOOL); 

	// pvData is validated inside synth while parsing.
	::EnterCriticalSection(&m_CriticalSection);
	if (m_pSynth)
	{
		hr = m_pSynth->Download(phDownload, pvData, pbFree);
	}
	::LeaveCriticalSection(&m_CriticalSection);

	return hr;
}

/* Unload
   Instructs the synthesizer to release an instrument or wave that was previously downloaded.
   This, in turn calls the synthesizer's instrument manager unload method (CInstManager::Unload), 
   where the implementation is more thoroughly documented.
*/

STDMETHODIMP CUserModeSynth::Unload(
	HANDLE hDownload,	// Handle to data, previously downloaded with a call to IDirectMusicSynth::Download.
	HRESULT ( CALLBACK *lpFreeHandle)(HANDLE,HANDLE), // If the original call to
						// IDirectMusicSynth::Download returned FALSE in pbFree,
						// the synthesizer hung onto the memory in the download chunk. If so,
						// the caller must be notified once the memory has been freed,
						// but that could occur later than IDirectMusicSynth::Download
						// since a wave might be currently in use. lpFreeHandle is a 
						// pointer to a callback
						// function which will be called when the memory is no longer in use.
	HANDLE hUserData)	// Pointer to user data, passed as a parameter to the 
						// lpFreeHandle function, typically used so the callback routine can retrieve
						// its state.
{
	HRESULT hr = DMUS_E_SYNTHNOTCONFIGURED;
	::EnterCriticalSection(&m_CriticalSection);
	if (m_pSynth)
	{
		hr = m_pSynth->Unload(hDownload, lpFreeHandle, hUserData);
	}
	::LeaveCriticalSection(&m_CriticalSection);
	return hr;
}

/* PlayBuffer
   Receives a buffer of MIDI events and parses them into individual events, which it
   then passes them on to the CSynth::PlayBuffer() method. Please see CSynth::PlayBuffer()
   for implementation details...
*/

STDMETHODIMP CUserModeSynth::PlayBuffer(
	REFERENCE_TIME rt,	// Start time of the buffer. This should be in 
						// REFERENCE_TIME units, relative to the master
						// clock, previously set with a call to IDirectMusicSynth::SetMasterClock.
						// And, this should be after the time returned by the clock in 
						// IDirectMusicSynth::GetLatencyClock.
	LPBYTE pbBuffer,	// Memory chunk with all the MIDI events, generated by IDirectMusicBuffer.
	DWORD cbBuffer)		// Size of buffer.
{
	class MIDIEVENT : public DMUS_EVENTHEADER {
	public:
		 BYTE  abEvent[4];           /* Actual event data, rounded up to be an even number */
									 /* of QWORD's (8 bytes) */
	};

	typedef class MIDIEVENT FAR  *LPMIDIEVENT;
	#define QWORD_ALIGN(x) (((x) + 7) & ~7)

	HRESULT hr;

    V_INAME(IDirectMusicSynth::PlayBuffer);
    V_BUFPTR_READ(pbBuffer,cbBuffer);

	::EnterCriticalSection(&m_CriticalSection);
    // Don't bother parsing the data if we aren't hooked up for output.
    if (!m_pSynthSink)
    {
	    ::LeaveCriticalSection(&m_CriticalSection);
        return DMUS_E_NOSYNTHSINK;
    }
    // Don't bother parsing the data if we aren't active.
    if (!m_fActive)
    {
	    ::LeaveCriticalSection(&m_CriticalSection);
        return DMUS_E_SYNTHINACTIVE;
    }

    LPMIDIEVENT lpEventHdr;
    DWORD cbEvent;
    // For each event in the buffer...
    while (cbBuffer)
    {
        // Check that the amount of data left is at least the minimum size.
        if (cbBuffer < sizeof(DMUS_EVENTHEADER))
        {
			::LeaveCriticalSection(&m_CriticalSection);
            return E_INVALIDARG;
        }

        lpEventHdr = (LPMIDIEVENT)pbBuffer; 
        cbEvent = DMUS_EVENT_SIZE(lpEventHdr->cbEvent);
        // If the size of the event takes us past the end of the buffer, report the error.
        if (cbEvent > cbBuffer)
        {
			::LeaveCriticalSection(&m_CriticalSection);
            return E_INVALIDARG;
        }
        
        // Move the buffer pointer forward by the size of the event and decrememnt the size left.
        pbBuffer += cbEvent;
        cbBuffer -= cbEvent;
        // Pass the raw event data to the synth. It will parse it further.
        hr = m_pSynth->PlayBuffer(m_pSynthSink, 
                                  rt + lpEventHdr->rtDelta, 
                                  &lpEventHdr->abEvent[0],
                                  lpEventHdr->cbEvent,
                                  lpEventHdr->dwChannelGroup);
        if (FAILED(hr))
        {
            ::LeaveCriticalSection(&m_CriticalSection);
            return hr;
        }
	}
	::LeaveCriticalSection(&m_CriticalSection);
	return S_OK;
}

/* GetPortCaps
   The GetPortCaps method is called in response to a call to IDirectMusic::EnumPort()
   or IDirectMusicPort::GetCaps().
   This fills in the passed DMUS_PORTCAPS structure with the synth's capabilities.

   When an application enumerates the available DirectMusic ports
   with a call to IDirectMusic::EnumPort(), DirectMusic calls
   each registered synth's IDirectMusicSynth::GetPortCaps() method. 
   This does mean that the additional overhead of creating and 
   initializing the synthesizer occurs with this call. So, it is a very good 
   idea to keep the overhead of simply creating a synthesizer to 
   a minimum, because there is a decent chance that it is being called
   only to interrogate its capabilities, and then it will be released.
*/

STDMETHODIMP CUserModeSynth::GetPortCaps(
	LPDMUS_PORTCAPS pCaps)	// DMUS_PORTCAPS structure to be filled in by synth.
{
	V_INAME(IDirectMusicSynth::GetPortCaps);
	V_STRUCTPTR_WRITE(pCaps,DMUS_PORTCAPS);

    wcscpy(pCaps->wszDescription,L"Microsoft DDK Synthesizer");

	pCaps->dwClass = DMUS_PC_OUTPUTCLASS;
    pCaps->dwType = DMUS_PORT_USER_MODE_SYNTH;
	pCaps->dwFlags = DMUS_PC_DLS | DMUS_PC_SOFTWARESYNTH | DMUS_PC_DIRECTSOUND;

    pCaps->guidPort = CLSID_DDKSynth;

	pCaps->dwMemorySize = DMUS_PC_SYSTEMMEMORY;
	pCaps->dwMaxChannelGroups = MAX_CHANNEL_GROUPS;
	pCaps->dwMaxVoices = MAX_VOICES;
	pCaps->dwMaxAudioChannels = 2;

    pCaps->dwEffectFlags = 0;

	return S_OK;
}

/* SetMasterClock
   The SetMasterClock method is optional. DirectMusic calls it and passes it the
   master IReferenceClock. If the synth needs this to keep in sync with the rest of 
   DirectMusic, it should AddRef() the clock and hang on to it. Our software synth
   doesn't need this since it accomplishes all its timing via communication with
   the SynthSink (IDirectMusicSynthSink.)

   The master clock is very different from the latency clock, which 
   is retrieved from the synth with a call to IDirectMusicSynth::GetLatencyClock.
   While the master clock provides the time base, the latency clock 
   simply tracks the progress of the synthesizer's render engine.  
   This enables the application to know the earliest time it can submit 
   an event for playback, via the IDirectMusicSynth::PlayBuffer command.
   The latency clock is tightly synchronized to the master
   clock, so its units are relative. 

   You can measure the latency of the synthesizer by comparing the
   time of the latency clock with the master clock. Note that the
   latency clock will have jitter, reflecting the bursty nature of
   synthesizer mixing, while the master clock must increment
   smoothly.
*/

STDMETHODIMP CUserModeSynth::SetMasterClock(
	IReferenceClock *pClock)	// Pointer to master IReferenceClock, 
								// used by all devices in current instance of DirectMusic.

{
	V_INAME(IDirectMusicSynth::SetMasterClock);
	V_INTERFACE(pClock);

	return S_OK;
}

/* GetLatencyClock
   This returns the latency clock created by the output audio sink 
   (IDirectMusicSynthSink) object, which handles the output audio stream.
   The latency clock returns the current render time whenever its 
   IReferenceClock::GetTime method is called. This time is always relative 
   to the time established by the master clock, provided by DMusic.
   The latency time is used by applications, including the IDirectMusicPerformance
   engine, to identify the next available time to start playing a note.
*/

STDMETHODIMP CUserModeSynth::GetLatencyClock(
	IReferenceClock **ppClock)	// <i IReferenceClock> interface designed to return the current mix time.

{
	V_INAME(IDirectMusicSynth::GetLatencyClock);
	V_PTR_WRITE(ppClock,IReferenceClock *);

    HRESULT hr = DMUS_E_NOSYNTHSINK;
    ::EnterCriticalSection(&m_CriticalSection);
	if (m_pSynthSink)
	{
		 hr = m_pSynthSink->GetLatencyClock(ppClock);
	}
    ::LeaveCriticalSection(&m_CriticalSection);
	return hr;
}

/* Activate
   The synthesizer can be told to enable or disable 
   the audio device under program control. This gives the
   application the ability to manage its use of resources. When
   it is not playing music, it can deactivate, and so free the
   wave out for other applications.

   The wave out resource is actually managed by a separate COM object, 
   represented by the IDirectMusicSynthSink interface. 
   This must be first connected with a call to IDirectMusicSynth::SetSynthSink. 
   Otherwise, the synthesizer is likely to fail the 
   IDirectMusicSynth::Activate call.

   In fact, activation is mostly managed by the wave sink object. 
   When IDirectMusicSynth::Activate is called, the 
   synth sets its internal activation state and calls 
   IDirectMusicSynthSink::Activate to enable or disable
   the wave output.
*/

STDMETHODIMP CUserModeSynth::Activate(
	BOOL fEnable)			// Whether to activate or deactivate audio.
{
	HRESULT hr = DMUS_E_SYNTHNOTCONFIGURED;

	if (fEnable)
	{
	    if (m_pSynthSink)
        {
		    if (!m_fActive)
		    {
			    if (m_dwSampleRate && m_dwChannels)
			    {
				    if (m_pSynth)
				    {
					    m_pSynth->Activate(m_dwSampleRate, m_dwChannels);

				        if (SUCCEEDED(m_pSynthSink->Activate(fEnable)))
				        {
					        m_fActive = TRUE;
                            hr = S_OK;
				        }
                        else
                        {
                            hr = E_UNEXPECTED;
                        }
				    }
			    }
		    }
            else
            {
                Trace(0, "CUserModeSynth::Activate- synth already active\n");
                hr = DMUS_E_SYNTHACTIVE;
            }
        }
        else
	    {
		    Trace(0, "CUserModeSynth::Activate- sink not connected\n");
            hr = DMUS_E_NOSYNTHSINK;
	    }
	}
	else
	{
		if (m_fActive)
		{
			m_fActive = FALSE;
			if (m_pSynth)
			{
				m_pSynth->Deactivate();
			}

			if (m_pSynthSink)
            {
			    if (SUCCEEDED(m_pSynthSink->Activate(fEnable)))
			    {
                    hr = S_OK;
			    }
                else
                {
                    hr = E_UNEXPECTED;
                }
            }
		}
        else
        {
            Trace(2, "CUserModeSynth::Activate- synth already inactive\n");
            hr = S_FALSE;
        }
	}
	return hr;
}

/* Render
   This is called by IDirectMusicSynthSink to render to the 
   audio stream, pointed to by pBuffer.

   Typically, a synthesizer manages converting messages into
   rendered wave data in two processes. First, it time stamps the MIDI 
   messages it receives from the application via calls to 
   IDirectMusicSynth::PlayBuffer and places them in its own
   internal queue. 

   Then, in response to IDirectMusicSynth::Render, it 
   generates audio by pulling MIDI messages from the queue and
   synthesizing the appropriate tones within
   the time span of the requested render buffer.

   As the synthesizer renders the MIDI messages into the buffer, it
   calls IDirectMusicSynthSink::RefTimeToSample to 
   translate the MIDI time stamps into sample positions. This 
   guarantees extremely accurate timing.
*/

STDMETHODIMP CUserModeSynth::Render(
	short *pBuffer,		// Pointer to buffer to write into. 
	DWORD dwLength,		// Length of buffer, in samples. This is not the  
						// memory size of the buffer. The memory size may vary,
						// dependant on the buffer format.
	LONGLONG llPosition)// Position in the audio stream, also in samples.
						// This should always increment by dwLength after
						// each call.
{
	V_INAME(IDirectMusicSynth::Render);
	V_BUFPTR_WRITE(pBuffer,dwLength << m_dwChannels);
	if (!m_pSynthSink)
	{
		return DMUS_E_SYNTHNOTCONFIGURED;
	}
    if (!m_fActive)
    {
        return DMUS_E_SYNTHINACTIVE;
    }

    ::EnterCriticalSection(&m_CriticalSection);
	if (m_pSynth)
	{
		m_pSynth->Mix(pBuffer,dwLength,llPosition);
	}
    ::LeaveCriticalSection(&m_CriticalSection);
	return S_OK;
}

STDMETHODIMP CUserModeSynth::SetChannelPriority(
    DWORD dwChannelGroup, 
    DWORD dwChannel, 
    DWORD dwPriority)
{
    if (m_pSynth)
    {
        return m_pSynth->SetChannelPriority(dwChannelGroup,dwChannel,dwPriority);
    }
    return E_FAIL;
}

STDMETHODIMP CUserModeSynth::GetChannelPriority(
    DWORD dwChannelGroup, 
    DWORD dwChannel, 
    LPDWORD pdwPriority)
{
    if (m_pSynth)
    {
        return m_pSynth->GetChannelPriority(dwChannelGroup,dwChannel,pdwPriority);
    }
    return E_FAIL;
}

/* GetFormat
   This returns the wave format that the synthesizer can write to.
   The WAVEFORMATEX structure can have a variable length that depends on
   the details of the format. So, before retrieving the format description,
   the caller queries the DirectMusicSynth object for the
   size of the format by calling this method and specifying NULL for the
   pWaveFormatEx parameter. The size of the structure is then returned in
   the pdwWaveFormatExSize parameter. The caller can then allocate
   sufficient memory and call IDirectMusicSynth::GetFormat again to
   retrieve the format description.

   If pWaveFormatEx parameter is not NULL, DirectMusic writes, at most,
   *pdwWaveFormatExSize bytes to pWaveFormatEx.
*/

STDMETHODIMP CUserModeSynth::GetFormat(
    LPWAVEFORMATEX pWaveFormatEx,
    LPDWORD pdwWaveFormatExSize)
{
	V_INAME(IDirectMusicSynth::GetFormat);
    V_PTR_WRITE(pdwWaveFormatExSize, DWORD);
	V_BUFPTR_WRITE_OPT(pWaveFormatEx, *pdwWaveFormatExSize);

    if (!m_pSynth)
    {
		return DMUS_E_SYNTHNOTCONFIGURED;
    }

    if (!pWaveFormatEx)
    {
        *pdwWaveFormatExSize = sizeof(WAVEFORMATEX);
        return S_OK;
    }

	WAVEFORMATEX wfx;
    memset(&wfx, 0, sizeof(wfx));
	wfx.wFormatTag = WAVE_FORMAT_PCM;
	wfx.nChannels = (WORD)m_dwChannels;
	wfx.nSamplesPerSec = (WORD)m_dwSampleRate;
	wfx.wBitsPerSample = 16;
    wfx.nBlockAlign = wfx.nChannels * (wfx.wBitsPerSample / 8);
	wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
	wfx.cbSize = 0; // no extra data

    memcpy(pWaveFormatEx, &wfx, min(sizeof(wfx), *pdwWaveFormatExSize));
    return S_OK;
}

/* GetAppend
   The synth may need to have an additional sample or more added to the end of
   each downloaded wave. This is so it can handle loop points. 
   The download mechanism needs to know this so it can allocate the appropriately
   sized buffer. GetAppend() is called by DMusic in the process of
   downloading an instrument.
*/

STDMETHODIMP CUserModeSynth::GetAppend(
    DWORD* pdwAppend)
{
	V_INAME(IDirectMusicSynth::GetAppend);
    V_PTR_WRITE(pdwAppend, DWORD);

    *pdwAppend = 2; // The synth needs 1 extra sample for loop interpolation.
                    // We're adding one more to be paranoid.
    return S_OK;
}

/* GetRunningStats
   Returns running status, accumulated over a one second time period.
   Includes peak volume, average number of voices, average cpu per voice,
   etc.
*/

STDMETHODIMP CUserModeSynth::GetRunningStats(
	LPDMUS_SYNTHSTATS pStats)	// DMUS_SYNTHSTATS structure to fill in.

{
	HRESULT hr = DMUS_E_SYNTHNOTCONFIGURED;
	V_INAME(IDirectMusicSynth::GetRunningStats);
	V_STRUCTPTR_WRITE(pStats,DMUS_SYNTHSTATS);

	if (m_pSynthSink)
	{
		::EnterCriticalSection(&m_CriticalSection);
		if (m_pSynth)
		{
			PerfStats Stats;
			m_pSynth->GetPerformanceStats(&Stats);
			long lCPU = Stats.dwCPU;
			if (Stats.dwVoices)
			{
				lCPU /= Stats.dwVoices;
			}
			else
			{
				lCPU = 0;
			}
			pStats->dwVoices = Stats.dwVoices;
			pStats->dwCPUPerVoice = lCPU * 10;
			pStats->dwTotalCPU = Stats.dwCPU * 10;
			pStats->dwLostNotes = Stats.dwNotesLost;
			long ldB = 6;
			double fLevel = Stats.dwMaxAmplitude;
			if (Stats.dwMaxAmplitude < 1)
			{
				fLevel = -96.0;
			}
			else
			{
				fLevel /= 32768.0;
				fLevel = log10(fLevel);
				fLevel *= 20.0;
			}
			pStats->lPeakVolume = (long) fLevel;
			pStats->dwValidStats = DMUS_SYNTHSTATS_VOICES | DMUS_SYNTHSTATS_TOTAL_CPU | 
				DMUS_SYNTHSTATS_CPU_PER_VOICE | DMUS_SYNTHSTATS_LOST_NOTES | DMUS_SYNTHSTATS_PEAK_VOLUME;
			hr = S_OK;
		}
		::LeaveCriticalSection(&m_CriticalSection);
	}
	return hr;
}

/* KsControl mechanism for getting and setting additional parameters on the synth.
   This extensible system is used to query the synth for all sorts of capabilities, including whether
   it supports DLS1, GS, and other formats. And, it can be used to control parameters like
   overall volume and reverb settings. Additional features can be accessed via this mechanism, 
   IDirectMusicPort will pass them through to the synth.
   To ease implementation, we use the GENERICPROPERTY structure, which can be configured for
   each type of query. An array of these is set up (immediately to follow) and is referenced
   by the KsProperty() method below.
*/

static DWORD dwPropFalse = FALSE;
static DWORD dwPropTrue  = TRUE;
static DWORD dwSystemMemory = DMUS_PC_SYSTEMMEMORY;

GENERICPROPERTY CUserModeSynth::m_aProperty[] = 
{      
    { 
        &GUID_DMUS_PROP_GM_Hardware,		// Does synth have its own GM set? FALSE
        0,                                  
        KSPROPERTY_SUPPORT_GET,             // This can be queried but not set
        GENPROP_F_STATIC,                  
        &dwPropFalse, sizeof(dwPropFalse),  // FALSE
        NULL                                // No method
    },
    {   &GUID_DMUS_PROP_GS_Hardware,		// Does synth have its own GS set? FALSE
        0,  
        KSPROPERTY_SUPPORT_GET,             // This can be queried but not set
        GENPROP_F_STATIC,
        &dwPropFalse, sizeof(dwPropFalse),  // FALSE
        NULL                                // No method
    },
    {   &GUID_DMUS_PROP_XG_Hardware,		// Does synth have its own XG set? FALSE
        0,  
        KSPROPERTY_SUPPORT_GET,             // This can be queried but not set
        GENPROP_F_STATIC,
        &dwPropFalse, sizeof(dwPropFalse),  // FALSE
        NULL                                // No method
    },
    {   &GUID_DMUS_PROP_XG_Capable,		    // Is synth capable of playing base XG? TRUE
        0,  
        KSPROPERTY_SUPPORT_GET,             // This can be queried but not set
        GENPROP_F_STATIC,
        &dwPropTrue, sizeof(dwPropTrue),    // TRUE
        NULL                                // No method
    },
    {   &GUID_DMUS_PROP_GS_Capable,		    // Is synth capable of playing base GS? TRUE
        0,  
        KSPROPERTY_SUPPORT_GET,             // This can be queried but not set
        GENPROP_F_STATIC,
        &dwPropTrue, sizeof(dwPropTrue),    // TRUE
        NULL
    },
    {   &GUID_DMUS_PROP_INSTRUMENT2,		// Does synth support newer download format? TRUE
        0,  
        KSPROPERTY_SUPPORT_GET,             // This can be queried but not set
        GENPROP_F_STATIC,
        &dwPropTrue, sizeof(dwPropTrue),    // TRUE
        NULL
    },
    { 
        &GUID_DMUS_PROP_DLS1,				// Does synth support DLS level 1? TRUE
        0,  
        KSPROPERTY_SUPPORT_GET,             // This can be queried but not set
        GENPROP_F_STATIC,
        &dwPropTrue,  sizeof(dwPropTrue),   // TRUE
        NULL
    },
    { 
        &GUID_DMUS_PROP_DLS2,				// Does synthe support DLS level 2? FALSE
        0,  
        KSPROPERTY_SUPPORT_GET,             // This can be queried but not set
        GENPROP_F_STATIC,
        &dwPropFalse,  sizeof(dwPropFalse), // FALSE
        NULL
    },
    { 
        &GUID_DMUS_PROP_SampleMemorySize,	// How much sample memory is available?	DMUS_PC_SYSTEMMEMORY
        0,  
        KSPROPERTY_SUPPORT_GET,             // This can be queried but not set
        GENPROP_F_STATIC,
        &dwSystemMemory,  sizeof(dwSystemMemory), // DMUS_PC_SYSTEMMEMORY
        NULL
    },
    {
        &KSPROPSETID_Synth,                 // Synth command
        KSPROPERTY_SYNTH_VOLUME,            // Set volume
        KSPROPERTY_SUPPORT_SET,             // This can be set, not gotten.
        GENPROP_F_FNHANDLER,              
        NULL, 0,
        CUserModeSynth::HandleSetVolume     // Call this method
    },
    {
        &KSPROPSETID_Synth,                 // Synth command
        KSPROPERTY_SYNTH_VOLUMEBOOST,       // Set additional volume boost
        KSPROPERTY_SUPPORT_SET,             
        GENPROP_F_FNHANDLER,                // Call handler
        NULL, 0,
        CUserModeSynth::HandleSetBoost      // Handle method
    },
    {
        &GUID_DMUS_PROP_WavesReverb,        // Set the reverb parameters
        0,
        KSPROPERTY_SUPPORT_SET,             // Can only be set
        GENPROP_F_FNHANDLER,
        NULL, 0,
        CUserModeSynth::HandleSetReverb     // Call this method
    },
    {
        &GUID_DMUS_PROP_Effects,            // Enable/Disable effects (only reverb.)
        0,
        KSPROPERTY_SUPPORT_SET | KSPROPERTY_SUPPORT_GET,
        GENPROP_F_FNHANDLER,
        NULL, 0,
        CUserModeSynth::HandleEffects       // Call method
    },
    {
        &GUID_DMUS_PROP_SamplePlaybackRate, // What is the synth playback rate?
        0,
        KSPROPERTY_SUPPORT_GET,             // This can be queried but not set
        GENPROP_F_FNHANDLER,
        NULL, 0,
        CUserModeSynth::HandleGetSampleRate // Call this method
    }
};

const int CUserModeSynth::m_nProperty = sizeof(m_aProperty) / sizeof(m_aProperty[0]);

HRESULT CUserModeSynth::HandleGetSampleRate(
        ULONG               ulId, 
        BOOL                fSet, 
        LPVOID              pbBuffer, 
        PULONG              pcbBuffer)
{
    if (*pcbBuffer != sizeof(LONG)) 
    {
        return E_INVALIDARG;
    }
    if (!fSet)
    {
        *(long*)pbBuffer = m_dwSampleRate;
    }
    return S_OK;
}

HRESULT CUserModeSynth::HandleSetVolume(
        ULONG               ulId, 
        BOOL                fSet, 
        LPVOID              pbBuffer, 
        PULONG              pcbBuffer)
{
    if (*pcbBuffer != sizeof(LONG)) 
    {
        return E_INVALIDARG;
    }
    
    m_lVolume =  *(LONG*)pbBuffer;
    m_lGainAdjust = m_lVolume + m_lBoost;

    if (m_pSynth)
    {
        m_pSynth->SetGainAdjust(m_lGainAdjust);
    }
    Trace(3, "lGainAdjust now %ld\n", m_lGainAdjust);

    return S_OK;
}

HRESULT CUserModeSynth::HandleSetBoost(
        ULONG               ulId, 
        BOOL                fSet, 
        LPVOID              pbBuffer, 
        PULONG              pcbBuffer)
{
    if (*pcbBuffer != sizeof(LONG)) 
    {
        return E_INVALIDARG;
    }

    m_lBoost =  *(LONG*)pbBuffer;
    m_lGainAdjust = m_lVolume + m_lBoost;

    if (m_pSynth)
    {
        m_pSynth->SetGainAdjust(m_lGainAdjust);
    }
    Trace(3, "lGainAdjust now %ld\n", m_lGainAdjust);

    return S_OK;
}

HRESULT CUserModeSynth::HandleSetReverb(ULONG ulId, BOOL fSet, LPVOID pbBuffer, PULONG pcbBuffer)

{
	DMUS_WAVES_REVERB_PARAMS *pParams;
    if (*pcbBuffer != sizeof(DMUS_WAVES_REVERB_PARAMS)) 
    {
        return E_INVALIDARG;
    }

	pParams = (DMUS_WAVES_REVERB_PARAMS *) pbBuffer;
    if (m_pSynth)
    {
        m_pSynth->SetReverb(pParams);
    }

    return S_OK;
}

HRESULT CUserModeSynth::HandleEffects(
        ULONG               ulId,
        BOOL                fSet, 
        LPVOID              pbBuffer, 
        PULONG              pcbBuffer)
{
    if (*pcbBuffer != sizeof(LONG)) 
    {
        return E_INVALIDARG;
    }
    if (fSet)
    {
        long lEffects = *(long*)pbBuffer;
    
        if (m_pSynth)
        {
            m_pSynth->SetReverbActive(lEffects & DMUS_EFFECT_REVERB);
        }
    }
    else
    {
        if (m_pSynth && m_pSynth->IsReverbActive())
        {
            *(long*)pbBuffer = DMUS_EFFECT_REVERB;
        }
        else
        {
            *(long*)pbBuffer = 0;
        }
    }
    return S_OK;
}

// 
// CDirectMusicEmulatePort::FindPropertyItem
//                         
// Given a GUID and an item ID, find the associated property item in the synth's
// table of SYNPROPERTY's.
//
// Returns a pointer to the entry or NULL if the item was not found.
//
GENERICPROPERTY *CUserModeSynth::FindPropertyItem(REFGUID rguid, ULONG ulId)
{
    GENERICPROPERTY *pPropertyItem = &m_aProperty[0];
    GENERICPROPERTY *pEndOfItems = pPropertyItem + m_nProperty;

    for (; pPropertyItem != pEndOfItems; pPropertyItem++)
    {
        if (*pPropertyItem->pguidPropertySet == rguid && 
             pPropertyItem->ulId == ulId)
        {
            return pPropertyItem;
        }
    }

    return NULL;
}

#define KS_VALID_FLAGS (KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_GET| KSPROPERTY_TYPE_BASICSUPPORT)

STDMETHODIMP CUserModeSynth::KsProperty(
    PKSPROPERTY pPropertyIn, ULONG ulPropertyLength,
    LPVOID pvPropertyData, ULONG ulDataLength,
    PULONG pulBytesReturned)
{
    V_INAME(DirectMusicSynthPort::IKsContol::KsProperty);
    V_BUFPTR_WRITE(pPropertyIn, ulPropertyLength);
    V_BUFPTR_WRITE_OPT(pvPropertyData, ulDataLength);
    V_PTR_WRITE(pulBytesReturned, ULONG);

    DWORD dwFlags = pPropertyIn->Flags & KS_VALID_FLAGS;

    GENERICPROPERTY *pProperty = FindPropertyItem(pPropertyIn->Set, pPropertyIn->Id);

    if (pProperty == NULL)
    {
        return DMUS_E_UNKNOWN_PROPERTY;
    }

    switch (dwFlags)
    {
        case KSPROPERTY_TYPE_GET:
            if (!(pProperty->ulSupported & KSPROPERTY_SUPPORT_GET))
            {
                return DMUS_E_GET_UNSUPPORTED;
            }

            if (pProperty->ulFlags & GENPROP_F_FNHANDLER)
            {
                GENPROPHANDLER pfn = pProperty->pfnHandler;
                *pulBytesReturned = ulDataLength;
                return (this->*pfn)(pPropertyIn->Id, FALSE, pvPropertyData, pulBytesReturned);
            }
    
            if (ulDataLength > pProperty->cbPropertyData)
            {
                ulDataLength = pProperty->cbPropertyData;
            }

            CopyMemory(pvPropertyData, pProperty->pPropertyData, ulDataLength);
            *pulBytesReturned = ulDataLength;

            return S_OK;

        case KSPROPERTY_TYPE_SET:
            if (!(pProperty->ulSupported & KSPROPERTY_SUPPORT_SET))
            {
                return DMUS_E_SET_UNSUPPORTED;
            }

            if (pProperty->ulFlags & GENPROP_F_FNHANDLER)
            {
                GENPROPHANDLER pfn = pProperty->pfnHandler;
                return (this->*pfn)(pPropertyIn->Id, TRUE, pvPropertyData, &ulDataLength);
            }

            if (ulDataLength > pProperty->cbPropertyData)
            {
                ulDataLength = pProperty->cbPropertyData;
            }

            CopyMemory(pProperty->pPropertyData, pvPropertyData, ulDataLength);

            return S_OK;
            

        case KSPROPERTY_TYPE_BASICSUPPORT:
            if (pProperty == NULL)
            {
                return DMUS_E_UNKNOWN_PROPERTY;
            }

            // XXX Find out what convention is for this!!
            //
            if (ulDataLength < sizeof(DWORD))
            {
                return E_INVALIDARG;
            }

            *(LPDWORD)pvPropertyData = pProperty->ulSupported;    
            *pulBytesReturned = sizeof(DWORD);
            
            return S_OK;
    }

    Trace(1, "KSProperty Flags must contain one of: %s\n"
              "\tKSPROPERTY_TYPE_SET, KSPROPERTY_TYPE_GET, or KSPROPERTY_TYPE_BASICSUPPORT\n");
    return E_INVALIDARG;
}

STDMETHODIMP CUserModeSynth::KsMethod(
    PKSMETHOD pMethod, ULONG ulMethodLength,
    LPVOID pvMethodData, ULONG ulDataLength,
    PULONG pulBytesReturned)
{
    V_INAME(DirectMusicSynth::IKsContol::KsMethod);
    V_BUFPTR_WRITE(pMethod, ulMethodLength);
    V_BUFPTR_WRITE_OPT(pvMethodData, ulDataLength);
    V_PTR_WRITE(pulBytesReturned, ULONG);

    return DMUS_E_UNKNOWN_PROPERTY;
}
STDMETHODIMP CUserModeSynth::KsEvent(
    PKSEVENT pEvent, ULONG ulEventLength,
    LPVOID pvEventData, ULONG ulDataLength,
    PULONG pulBytesReturned)
{
    V_INAME(DirectMusicSynthPort::IKsContol::KsEvent);
    V_BUFPTR_WRITE(pEvent, ulEventLength);
    V_BUFPTR_WRITE_OPT(pvEventData, ulDataLength);
    V_PTR_WRITE(pulBytesReturned, ULONG);
    
    return DMUS_E_UNKNOWN_PROPERTY;
}

// User mode registry helper
//
BOOL GetRegValueDword(
    LPCTSTR szRegPath,
    LPCTSTR szValueName,
    LPDWORD pdwValue)
{
	HKEY  hKeyOpen;
	DWORD dwType;
	DWORD dwCbData;
	LONG  lResult;
	BOOL  fReturn = FALSE;

	assert(pdwValue);

    lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
                           szRegPath,
                           0, KEY_QUERY_VALUE, 
                           &hKeyOpen );

	if (lResult == ERROR_SUCCESS)
	{
        dwCbData = sizeof(DWORD);

		lResult = RegQueryValueEx(hKeyOpen,
                                  szValueName, 
                                  NULL,
                                  &dwType,
                                  (LPBYTE)pdwValue,
                                  &dwCbData);

		if (lResult == ERROR_SUCCESS &&
            dwType == REG_DWORD)
		{
			fReturn = TRUE;
		}

		RegCloseKey( hKeyOpen );
	}

    return fReturn;
}

DWORD GetTheCurrentTime()
{
	static BOOL s_fFirstTime = TRUE;
	static LARGE_INTEGER s_liPerfFrequency;
	static BOOL s_fUsePerfCounter = FALSE;
	if (s_fFirstTime)
	{
		s_fFirstTime = FALSE;
		s_fUsePerfCounter = QueryPerformanceFrequency(&s_liPerfFrequency);
		s_liPerfFrequency.QuadPart /= 1000;
	}
	if (s_fUsePerfCounter)
	{
		LARGE_INTEGER liPerfCounter;
		QueryPerformanceCounter(&liPerfCounter);
		liPerfCounter.QuadPart /= s_liPerfFrequency.QuadPart;
		return (DWORD) liPerfCounter.QuadPart;
	}
	else
	{
		return timeGetTime();
	}
}

