//
//      Copyright (c) 1996-2000 Microsoft Corporation.  All rights reserved.
//      CSynth.cpp
//

#include "common.h"
#include "fltsafe.h"

#define STR_MODULENAME "DDKSynth.sys:CSynth: "

#pragma code_seg()
/*****************************************************************************
 * CSynth::CSynth()
 *****************************************************************************
 * Contructor for CSynth object.  Initialize the voice list, the stereo mode,
 * sample rate, performance statistics, etc.
 */
CSynth::CSynth()
{
    FLOATSAFE fs;

    DWORD nIndex;
    CVoice *pVoice;

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

    for (nIndex = 0;nIndex < MAX_NUM_VOICES;nIndex++)
    {
        pVoice = new CVoice;
        if (pVoice != NULL)
        {
            m_VoicesFree.AddHead(pVoice);
        }
    }
    for (nIndex = 0;nIndex < NUM_EXTRA_VOICES;nIndex++)
    {
        pVoice = new CVoice;
        if (pVoice != NULL)
        {
            m_VoicesExtra.AddHead(pVoice);
        }
    }
	m_ppControl = NULL;
	m_dwControlCount = 0;
    m_nMaxVoices = MAX_NUM_VOICES;
    m_nExtraVoices = NUM_EXTRA_VOICES; 
    m_stLastStats = 0;
    m_fAllowPanWhilePlayingNote = TRUE;
    m_fAllowVolumeChangeWhilePlayingNote = TRUE;
    ResetPerformanceStats();
    m_dwSampleRate = 22050;
    m_dwStereo = 1;
	m_stLastTime = 0;    
	SetSampleRate(SAMPLE_RATE_22);
    SetStereoMode(2);
    SetGainAdjust(600);
}

/*****************************************************************************
 * CSynth::~CSynth()
 *****************************************************************************
 * Destructor for CSynth object.  Delete the voices in the lists.
 */
CSynth::~CSynth()
{
    CVoice *pVoice;
    if (m_fCSInitialized)
    {
        // If CS never initialized, nothing else will have been set up
        //
        Close();

        while (pVoice = m_VoicesInUse.RemoveHead())
        {
            delete pVoice;
        }
        while (pVoice = m_VoicesFree.RemoveHead())
        {
            delete pVoice;
        }
        while (pVoice = m_VoicesExtra.RemoveHead())
        {
            delete pVoice;
        }
        DeleteCriticalSection(&m_CriticalSection);
    }
}

/*****************************************************************************
 * ChangeVoiceCount()
 *****************************************************************************
 * Change the number of voices in a given voice list.
 */
static short ChangeVoiceCount(CVoiceList *pList,short nOld,short nCount)
{
    if (nCount > nOld)
    {
        short nNew = nCount - nOld;
        for (;nNew != 0; nNew--)
        {
            CVoice *pVoice = new CVoice;
            if (pVoice != NULL)
            {
                pList->AddHead(pVoice);
            }
        }
    }
    else
    {
        short nNew = nOld - nCount;
        for (;nNew > 0; nNew--)
        {
            CVoice *pVoice = pList->RemoveHead();
            if (pVoice != NULL)
            {
                delete pVoice;
            }
            else 
            {
                nCount += nNew;
                break;
            }
        }
    }
    return nCount;
}

/*****************************************************************************
 * CSynth::SetMaxVoices()
 *****************************************************************************
 * Set the maximum number of voices available.
 */
HRESULT CSynth::SetMaxVoices(short nVoices,short nTempVoices)
{
    if (nVoices < 1)
    {
        nVoices = 1;
    }
    if (nTempVoices < 1)
    {
        nTempVoices = 1;
    }
    ::EnterCriticalSection(&m_CriticalSection);
    m_nMaxVoices = ChangeVoiceCount(&m_VoicesFree,m_nMaxVoices,nVoices);
    m_nExtraVoices = ChangeVoiceCount(&m_VoicesExtra,m_nExtraVoices,nTempVoices);
    ::LeaveCriticalSection(&m_CriticalSection);
    return S_OK;
}

/*****************************************************************************
 * CSynth::SetNumChannelGroups()
 *****************************************************************************
 * Set the number of channel groups (virtual MIDI cables).  For each channel 
 * group, there is a separate CControlLogic object.
 */
HRESULT CSynth::SetNumChannelGroups(DWORD dwCableCount)
{
	HRESULT hr = S_OK;
	CControlLogic **ppControl;
	if ((dwCableCount < 1) || (dwCableCount > MAX_CHANNEL_GROUPS))
	{
		return E_INVALIDARG;
	}

    ::EnterCriticalSection(&m_CriticalSection);
	if (m_dwControlCount != dwCableCount)
	{
		ppControl = new(NonPagedPool,'PSmD') CControlLogic *[dwCableCount]; //  DmSP
		if (ppControl)
		{
			DWORD dwX;
			for (dwX = 0; dwX < dwCableCount; dwX++)
			{
				ppControl[dwX] = NULL;
			}
			if (m_dwControlCount < dwCableCount)
			{
				for (dwX = 0; dwX < m_dwControlCount; dwX++)
				{
					ppControl[dwX] = m_ppControl[dwX];
				}
				for (;dwX < dwCableCount; dwX++)
				{
					ppControl[dwX] = new(NonPagedPool,'CSmD') CControlLogic;    //  DmSC
					if (ppControl[dwX])
					{
						hr = ppControl[dwX]->Init(&m_Instruments, this);
						if (FAILED(hr))
						{
							delete ppControl[dwX];
							ppControl[dwX] = NULL;
							dwCableCount = dwX;
							break;
						}

                        ppControl[dwX]->SetGainAdjust(m_vrGainAdjust);
					}
					else
					{
						dwCableCount = dwX;
						break;
					}
				}
			}
			else
			{
				AllNotesOff();
				for (dwX = 0; dwX < dwCableCount; dwX++)
				{
					ppControl[dwX] = m_ppControl[dwX];
				}
				for (; dwX < m_dwControlCount; dwX++)
				{
					if (m_ppControl[dwX])
					{
						delete m_ppControl[dwX];
					}
				}
			}
			if (m_ppControl)
			{
				delete m_ppControl;
			}
			m_ppControl = ppControl;
			m_dwControlCount = dwCableCount;
		}
		else
		{
			hr = E_OUTOFMEMORY;
		}
	}
    ::LeaveCriticalSection(&m_CriticalSection);
    
	return hr;
}

/*****************************************************************************
 * CSynth::SetGainAdjust()
 *****************************************************************************
 * Set the gain for the overall synth.  Set gain on each CControlLogic object.
 */
void CSynth::SetGainAdjust(VREL vrGainAdjust)
{
    DWORD idx;

    m_vrGainAdjust = vrGainAdjust;
	::EnterCriticalSection(&m_CriticalSection);
    
    for (idx = 0; idx < m_dwControlCount; idx++)
    {
        m_ppControl[idx]->SetGainAdjust(m_vrGainAdjust);
    }

    ::LeaveCriticalSection(&m_CriticalSection);
}

/*****************************************************************************
 * CSynth::Open()
 *****************************************************************************
 * Open the synth with the given number of channel groups.
 */
HRESULT CSynth::Open(DWORD dwCableCount, DWORD dwVoices)
{
	HRESULT hr = S_OK;
	if ((dwCableCount < 1) || (dwCableCount > MAX_CHANNEL_GROUPS))
	{
		return E_INVALIDARG;
	}
    ::EnterCriticalSection(&m_CriticalSection);
	hr = SetNumChannelGroups(dwCableCount);
	if (SUCCEEDED(hr))
	{
		short nTemp = (short) dwVoices / 4;
		if (nTemp < 4) nTemp = 4;
		SetMaxVoices((short) dwVoices, nTemp);
	}


    m_vrGainAdjust = 0;
    ::LeaveCriticalSection(&m_CriticalSection);
	return hr;
}

/*****************************************************************************
 * CSynth::Close()
 *****************************************************************************
 * Close down the synth:, silence it, delete the list of CControlLogic objects.
 */
HRESULT CSynth::Close()
{
    ::EnterCriticalSection(&m_CriticalSection);
	AllNotesOff();
	DWORD dwX;
	for (dwX = 0; dwX < m_dwControlCount; dwX++)
	{
		if (m_ppControl[dwX])
		{
			delete m_ppControl[dwX];
		}
	}
	m_dwControlCount = 0;
	if (m_ppControl)
	{
		delete [] m_ppControl;
		m_ppControl = NULL;
	}
    m_stLastStats = 0;
	m_stLastTime = 0;


    ::LeaveCriticalSection(&m_CriticalSection);
	return S_OK;
}
   
/*****************************************************************************
 * CSynth::GetMaxVoices()
 *****************************************************************************
 * Returns the maximum number of voices available.
 */
HRESULT CSynth::GetMaxVoices( 
    short * pnMaxVoices,    // Returns maximum number of allowed voices for continuous play.
    short * pnTempVoices )  // Returns number of extra voices for voice overflow.
{
    if (pnMaxVoices != NULL)
    {
        *pnMaxVoices = m_nMaxVoices;
    }
    if (pnTempVoices != NULL)
    {
        *pnTempVoices = m_nExtraVoices;
    }
    return S_OK;
}

/*****************************************************************************
 * CSynth::SetSampleRate()
 *****************************************************************************
 * Set the sample rate of the synth.  This silences the synth.  The SR is 
 * forwarded to the instrument manager.
 */
HRESULT CSynth::SetSampleRate(DWORD dwSampleRate)     
{
    HRESULT hr = S_OK;
	::EnterCriticalSection(&m_CriticalSection);
	AllNotesOff();
	m_stLastTime *= dwSampleRate;
	m_stLastTime /= m_dwSampleRate;
	// m_stLastTime = MulDiv(m_stLastTime,dwSampleRate,m_dwSampleRate);
	m_stLastStats = 0;
	m_dwSampleRate = dwSampleRate;
	m_stMinSpan = dwSampleRate / 100;   // 10 ms.
	m_stMaxSpan = (dwSampleRate + 19) / 20;    // 50 ms.
	::LeaveCriticalSection(&m_CriticalSection);
	m_Instruments.SetSampleRate(dwSampleRate);
    return hr;
}

/*****************************************************************************
 * CSynth::Activate()
 *****************************************************************************
 * Make the synth active.
 */
HRESULT CSynth::Activate(DWORD dwSampleRate, DWORD dwChannels )
{
	m_stLastTime = 0;
	SetSampleRate(dwSampleRate);
	SetStereoMode(dwChannels);
	ResetPerformanceStats();
	return S_OK;
}

/*****************************************************************************
 * CSynth::Deactivate()
 *****************************************************************************
 * Gag the synth.
 */
HRESULT CSynth::Deactivate()
{
	AllNotesOff();
	return S_OK;
}

/*****************************************************************************
 * CSynth::GetPerformanceStats()
 *****************************************************************************
 * Get the latest perf statistics.
 */
HRESULT CSynth::GetPerformanceStats(PerfStats *pStats)
{
    if (pStats == NULL)
    {
        return E_POINTER;
    }
    *pStats = m_CopyStats;
    return (S_OK);
}

/*****************************************************************************
 * CSynth::Mix()
 *****************************************************************************
 * Mix into the given buffer.  This is called by Render in the software
 * synth case, or this could be called by a request from hardware.
 */
void CSynth::Mix(short *pBuffer,DWORD dwLength,LONGLONG llPosition)
{
    PAGED_CODE();

    FLOATSAFE fs;

    STIME stEndTime;
    CVoice *pVoice;
    CVoice *pNextVoice;
    long lNumVoices = 0;
    ::EnterCriticalSection(&m_CriticalSection);

    LONG    lTime = - (LONG)::GetTheCurrentTime();

    stEndTime = llPosition + dwLength;
	StealNotes(stEndTime);
	DWORD dwX;
	for (dwX = 0; dwX < m_dwControlCount; dwX++)
	{
		m_ppControl[dwX]->QueueNotes(stEndTime);
	}
    pVoice = m_VoicesInUse.GetHead();
    for (;pVoice != NULL;pVoice = pNextVoice)
    {
        pNextVoice = pVoice->GetNext();

        pVoice->Mix(pBuffer,dwLength,llPosition,stEndTime);
        lNumVoices++;

        if (pVoice->m_fInUse == FALSE) 
        {
            m_VoicesInUse.Remove(pVoice);
            m_VoicesFree.AddHead(pVoice);
           // m_BuildStats.dwTotalSamples += (pVoice->m_stStopTime - pVoice->m_stStartTime);
			if (pVoice->m_stStartTime < m_stLastStats)
			{
				m_BuildStats.dwTotalSamples += (long) (pVoice->m_stStopTime - m_stLastStats);
			}
			else
			{
				m_BuildStats.dwTotalSamples += (long) (pVoice->m_stStopTime - pVoice->m_stStartTime);
			}
        }
    }
	for (dwX = 0; dwX < m_dwControlCount; dwX++)
	{
		m_ppControl[dwX]->ClearMIDI(stEndTime);
	}
    FinishMix(pBuffer,dwLength);
    if (stEndTime > m_stLastTime)
    {
        m_stLastTime = stEndTime;
    }
    lTime += ::GetTheCurrentTime();

    m_BuildStats.dwTotalTime += lTime;

    if ((m_stLastStats + m_dwSampleRate) <= m_stLastTime)
    {
        DWORD dwElapsed = (DWORD) (m_stLastTime - m_stLastStats);
		pVoice = m_VoicesInUse.GetHead();
		for (;pVoice != NULL;pVoice = pVoice->GetNext())
		{
			if (pVoice->m_stStartTime < m_stLastStats)
			{
				m_BuildStats.dwTotalSamples += dwElapsed;
			}
			else
			{
				m_BuildStats.dwTotalSamples += (long) (m_stLastTime - pVoice->m_stStartTime);
			}
		}
        if (dwElapsed == 0) dwElapsed = 1;
        if (m_BuildStats.dwTotalSamples == 0) m_BuildStats.dwTotalSamples = 1;
        m_BuildStats.dwVoices = 
            (m_BuildStats.dwTotalSamples + (dwElapsed >> 1)) / dwElapsed;
		{
			m_BuildStats.dwCPU = MulDiv(m_BuildStats.dwTotalTime,
				m_dwSampleRate, dwElapsed);
		}
        m_CopyStats = m_BuildStats;
        RtlZeroMemory(&m_BuildStats, sizeof(m_BuildStats));
        m_stLastStats = m_stLastTime;
    }
    ::LeaveCriticalSection(&m_CriticalSection);
}

/*****************************************************************************
 * CSynth::OldestVoice()
 *****************************************************************************
 * Get the most likely candidate to be shut down, to support voice stealing.
 * Priority is looked at first, then age.
 */
CVoice *CSynth::OldestVoice()
{
    CVoice *pVoice;
    CVoice *pBest = NULL;
    pVoice = m_VoicesInUse.GetHead();
    pBest = pVoice;
    if (pBest)
    {
        pVoice = pVoice->GetNext();
        for (;pVoice;pVoice = pVoice->GetNext())
        {
		    if (!pVoice->m_fTag)
		    {
			    if (pBest->m_fTag)
			    {
				    pBest = pVoice;
			    }
			    else
			    {
                    if (pVoice->m_dwPriority <= pBest->m_dwPriority)
                    {
				        if (pVoice->m_fNoteOn) 
				        {
					        if (pBest->m_fNoteOn)
					        {
						        if (pBest->m_stStartTime > pVoice->m_stStartTime)
						        {
							        pBest = pVoice;
						        }
					        }
				        }
				        else
				        {
					        if (pBest->m_fNoteOn ||
						        (pBest->m_vrVolume > pVoice->m_vrVolume))
					        {
						        pBest = pVoice;
					        }
				        }
                    }
			    }
		    }
        }
        if (pBest->m_fTag)
        {
            pBest = NULL;
        }
    }
    return pBest;
}

/*****************************************************************************
 * CSynth::StealVoice()
 *****************************************************************************
 * Steal a voice, if possible.  If none are at or below this priority, then
 * return NULL, and this voice will go unheard.  If there IS a voice to be 
 * stolen, silence it first.
 */
CVoice *CSynth::StealVoice(DWORD dwPriority)
{
    CVoice *pVoice;
    CVoice *pBest = NULL;
    pVoice = m_VoicesInUse.GetHead();
    for (;pVoice != NULL;pVoice = pVoice->GetNext())
    {
        if (pVoice->m_dwPriority <= dwPriority)
        {
            if (!pBest)
            {
                pBest = pVoice;
            }
            else
            {
                if (pVoice->m_fNoteOn == FALSE) 
                {
                    if ((pBest->m_fNoteOn == TRUE) ||
                        (pBest->m_vrVolume > pVoice->m_vrVolume))
                    {
                        pBest = pVoice;
                    }
                }
                else
                {
                    if (pBest->m_stStartTime > pVoice->m_stStartTime)
                    {
                        pBest = pVoice;
                    }
                }
            }
        }
    }
    if (pBest != NULL)
    {
        pBest->ClearVoice();
        pBest->m_fInUse = FALSE; 
        m_VoicesInUse.Remove(pBest);
        pBest->SetNext(NULL);
    }
    return pBest;
}

/*****************************************************************************
 * CSynth::QueueVoice()
 *****************************************************************************
 * This method queues a voice in the list of currently 
 * synthesizing voices. It places them in the queue so that
 * the higher priority voices are later in the queue. This
 * allows the note stealing algorithm to take off the top of
 * the queue.
 * And, we want older playing notes to be later in the queue
 * so the note ons and offs overlap properly. So, the queue is
 * sorted in priority order with older notes later within one
 * priority level.
 */
void CSynth::QueueVoice(CVoice *pVoice)
{
    CVoice *pScan = m_VoicesInUse.GetHead();
    CVoice *pNext = NULL;
    if (!pScan) // Empty list?
    {
        m_VoicesInUse.AddHead(pVoice);
        return;
    }
    if (pScan->m_dwPriority > pVoice->m_dwPriority)
    {   // Are we lower priority than the head of the list?
        m_VoicesInUse.AddHead(pVoice);
        return;
    }

    pNext = pScan->GetNext();
    for (;pNext;)
    {
        if (pNext->m_dwPriority > pVoice->m_dwPriority)
        {
            // Lower priority than next in the list.
            pScan->SetNext(pVoice);
            pVoice->SetNext(pNext);
            return;
        }
        pScan = pNext;
        pNext = pNext->GetNext();
    }
    // Reached the end of the list.
    pScan->SetNext(pVoice);
    pVoice->SetNext(NULL);
}

/*****************************************************************************
 * CSynth::StealNotes()
 *****************************************************************************
 * Clear out notes at a given time.
 */
void CSynth::StealNotes(STIME stTime)
{
    CVoice *pVoice;
    long lToMove = m_nExtraVoices - m_VoicesExtra.GetCount();
    if (lToMove > 0)
    {
        for (;lToMove > 0;)
        {
            pVoice = m_VoicesFree.RemoveHead();
            if (pVoice != NULL)
            {
                m_VoicesExtra.AddHead(pVoice);
                lToMove--;
            }
            else break;
        }
        if (lToMove > 0)
        {
            pVoice = m_VoicesInUse.GetHead();
            for (;pVoice;pVoice = pVoice->GetNext())
            {
                if (pVoice->m_fTag) // Voice is already slated to be returned.
                {
                    lToMove--;
                }
            }
            for (;lToMove > 0;lToMove--)
            {
                pVoice = OldestVoice();
                if (pVoice != NULL)
                {
                    pVoice->QuickStopVoice(stTime);
				    m_BuildStats.dwNotesLost++;
                }
                else break;
            }
        }
    }
}


/*****************************************************************************
 * CSynth::FinishMix()
 *****************************************************************************
 * Cleanup after the mix.
 */
void CSynth::FinishMix(short *pBuffer,DWORD dwLength)
{
    DWORD dwIndex;
	long lMax = (long) m_BuildStats.dwMaxAmplitude;
	long lTemp;
    for (dwIndex = 0; dwIndex < (dwLength << m_dwStereo); dwIndex++)
    {
		lTemp = pBuffer[dwIndex];
		lTemp <<= 1;
		if (lTemp < -32767) lTemp = -32767;
		if (lTemp > 32767) lTemp = 32767;
		pBuffer[dwIndex] = (short) lTemp;
		if (lTemp > lMax)
		{
			lMax = lTemp;
		}
    }
	m_BuildStats.dwMaxAmplitude = lMax;
}

/*****************************************************************************
 * CSynth::Unload()
 *****************************************************************************
 * Unload a previous download.  Forward the request to the instrument manager.
 */
HRESULT CSynth::Unload(HANDLE hDownload,
					   HRESULT ( CALLBACK *lpFreeMemory)(HANDLE,HANDLE),
					   HANDLE hUserData)
{
	return m_Instruments.Unload( hDownload, lpFreeMemory, hUserData);
}

/*****************************************************************************
 * CSynth::Download()
 *****************************************************************************
 * Handle a download.  Forward the request to the instrument manager.
 */
HRESULT CSynth::Download(LPHANDLE phDownload, void * pdwData, LPBOOL bpFree) 
{
    FLOATSAFE fs;

	return m_Instruments.Download( phDownload, (DWORD *) pdwData,  bpFree);
}

/*****************************************************************************
 * CSynth::PlayBuffer()
 *****************************************************************************
 * This receives one MIDI message in the form of a buffer of data and
 * ulCable, which indicates which Channel Group the message is addressed 
 * to. Each channel group is implemented with an instance of a CControlLogic
 * object, so this chooses which CControlLogic object to send the message
 * to. If ulCable is 0, this is a broadcast message and should be sent to all
 * CControlLogics.
 * 
 * PlayBuffer() analyzes the message and, depending on the size, either
 * sends to CControlLogic::RecordMIDI() or CControlLogic::RecordSysEx().
 * 
 * In order to properly associate the time stamp of the MIDI 
 * message in the buffer, the synth needs to convert from the
 * REFERENCE_TIME format to its internal sample based time. Since
 * the wave out stream is actually managed by IDirectMusicSynthSink,
 * the synth calls IDirectMusicSynthSink::RefTimeToSample
 * for each MIDI message to convert its time stamp into sample time.
 *
 * So, typically, the synthesizer pulls each MIDI message from the
 * buffer, stamps it in sample time, then places it in its own
 * internal queue. The queue is emptied later by the rendering
 * process, which is managed by CDmSynthStream::Render and
 * called by IDirectMusicSynthSink.
 */
HRESULT	CSynth::PlayBuffer(IDirectMusicSynthSink *pSynthSink,REFERENCE_TIME rt, 
                           LPBYTE lpBuffer, DWORD cbBuffer, ULONG ulCable)
{
	STIME stTime;

	::EnterCriticalSection(&m_CriticalSection);

	if ( rt == 0 ) // Special case of time == 0.
	{
		stTime = m_stLastTime;
	}
	else
	{
		pSynthSink->RefTimeToSample(rt, &stTime);
	}

    if (cbBuffer <= sizeof(DWORD))
    {
		if (ulCable <= m_dwControlCount)
		{
			if (ulCable == 0) // Play all groups if 0.
			{
				for (; ulCable < m_dwControlCount; ulCable++)
				{
					m_ppControl[ulCable]->RecordMIDI(stTime,lpBuffer[0], 
						lpBuffer[1], lpBuffer[2]);
				}
			}
			else
			{
				m_ppControl[ulCable - 1]->RecordMIDI(stTime,lpBuffer[0], 
				lpBuffer[1], lpBuffer[2]);

			}
		}
        else
        {
            Trace(1,"MIDI event on channel group %ld is beyond range of %ld opened channel groups\n",
                ulCable, m_dwControlCount);
        }
    }
    else
    {
		if (ulCable <= m_dwControlCount)
		{
			if (ulCable == 0)
			{
				for (; ulCable < m_dwControlCount; ulCable++)
				{
					m_ppControl[ulCable]->RecordSysEx(cbBuffer,
						&lpBuffer[0], stTime);
				}
			}
			else
			{
				m_ppControl[ulCable-1]->RecordSysEx(cbBuffer,
					&lpBuffer[0], stTime);
			}
		}
	}

	::LeaveCriticalSection(&m_CriticalSection);
	return S_OK;
}

/*****************************************************************************
 * CSynth::SetStereoMode()
 *****************************************************************************
 * Set the stereo/mono mode for this synth.
 */
HRESULT CSynth::SetStereoMode(DWORD dwChannels)   // 1 for Mono, 2 for Stereo.
{
    HRESULT hr = S_OK;
    if ((m_dwStereo + 1) != dwChannels)
    {
		DWORD dwStereo;
		if (dwChannels > 1) dwStereo = 1;
		else dwStereo = 0;
		if (dwStereo != m_dwStereo)
		{
			m_dwStereo = dwStereo;
		}
    }
    return hr;
}

/*****************************************************************************
 * CSynth::ResetPerformanceStats()
 *****************************************************************************
 * Reset the running performance statistics.
 */
void CSynth::ResetPerformanceStats()
{
    m_BuildStats.dwNotesLost = 0;
    m_BuildStats.dwTotalTime = 0;
    m_BuildStats.dwVoices = 0;
    m_BuildStats.dwTotalSamples = 0;
    m_BuildStats.dwCPU = 0;
    m_BuildStats.dwMaxAmplitude = 0;
    m_CopyStats = m_BuildStats;
}

/*****************************************************************************
 * CSynth::AllNotesOff()
 *****************************************************************************
 * Stop all voices.
 */
HRESULT CSynth::AllNotesOff()
{
    CVoice *pVoice;
    ::EnterCriticalSection(&m_CriticalSection);
    while (pVoice = m_VoicesInUse.RemoveHead())
    {
        pVoice->ClearVoice();
        pVoice->m_fInUse = FALSE; 
        m_VoicesFree.AddHead(pVoice);

        long lSamples;

        if (pVoice->m_stStartTime < m_stLastStats)
        {
            lSamples = (long) (pVoice->m_stStopTime - m_stLastStats);
        }
        else
        {
            lSamples = (long) (pVoice->m_stStopTime - pVoice->m_stStartTime);
        }
        if (lSamples < 0) 
        {
            lSamples = 0;
        }
        m_BuildStats.dwTotalSamples += lSamples;
    }
    ::LeaveCriticalSection(&m_CriticalSection);
    return (S_OK);
}

/*****************************************************************************
 * CSynth::SetChannelPriority()
 *****************************************************************************
 * Set the priority for a given channel, to be used in voice stealing.
 */
HRESULT CSynth::SetChannelPriority(DWORD dwChannelGroup,DWORD dwChannel,
                                   DWORD dwPriority)
{
	HRESULT hr = S_OK;

	::EnterCriticalSection(&m_CriticalSection);
    
    dwChannelGroup--;
	if ((dwChannelGroup >= m_dwControlCount) || (dwChannel > 15))
	{
		hr = E_INVALIDARG;
	}
	else
	{
		if (m_ppControl)
		{
			hr = m_ppControl[dwChannelGroup]->SetChannelPriority(dwChannel,dwPriority);
		}
	}
    ::LeaveCriticalSection(&m_CriticalSection);

    return hr;
}

/*****************************************************************************
 * CSynth::GetChannelPriority()
 *****************************************************************************
 * Retrieve the priority of a given channel/channel group, to be used to
 * facilitate correct voice stealing.
 */
HRESULT CSynth::GetChannelPriority(DWORD dwChannelGroup, DWORD dwChannel, 
                                   LPDWORD pdwPriority)
{
	HRESULT hr = S_OK;

	::EnterCriticalSection(&m_CriticalSection);

    dwChannelGroup--;
	if ((dwChannelGroup >= m_dwControlCount) || (dwChannel > 15))
	{
		hr = E_INVALIDARG;
	}
	else
	{
		if (m_ppControl)
		{
			hr = m_ppControl[dwChannelGroup]->GetChannelPriority(dwChannel,pdwPriority);
		}
	}
    ::LeaveCriticalSection(&m_CriticalSection);

    return hr;
}