//      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)
//      CSynth.cpp
//      Copyright (c) Microsoft Corporation	1996, 1997, 1998
//

#ifdef DMSYNTH_MINIPORT
#include "common.h"
#else 

#include "simple.h"
#include <mmsystem.h>
#include <dmusicc.h>
#include <dmusics.h>
#include "synth.h"
#include "CSynth.h"
#ifdef REVERB_ENABLED
#include "sverb.h"
#endif
#endif

CSynth::CSynth()

{
    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_fReverbActive = FALSE;
    m_pCoefs = NULL;
    m_pStates = NULL;
	m_ReverbParams.fInGain = 0.0;
	m_ReverbParams.fReverbMix = -10.0;
	m_ReverbParams.fReverbTime = 1000.0;
	m_ReverbParams.fHighFreqRTRatio	= (float) 0.001;
	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()

{
    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);
    }
}

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;
}

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;
}

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)
	{
        try
        {
		    ppControl = new CControlLogic *[dwCableCount];
        }
        catch( ... )
        {
            ppControl = NULL;
        }

		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++)
				{
                    try
                    {
					    ppControl[dwX] = new CControlLogic;
                    }
                    catch( ... )
                    {
                        ppControl[dwX] = NULL;
                    }

					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;
}

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);
}

HRESULT CSynth::SetReverb(DMUS_WAVES_REVERB_PARAMS *pParams)

{
	m_ReverbParams = *pParams;
	if (m_pCoefs)
	{
#ifdef REVERB_ENABLED
		::SetSVerb(m_ReverbParams.fInGain,m_ReverbParams.fReverbMix,
			m_ReverbParams.fReverbTime,m_ReverbParams.fHighFreqRTRatio,m_pCoefs );
#endif
    }
	return S_OK;;
}

void CSynth::SetReverbActive(BOOL fReverb)

{
    ::EnterCriticalSection(&m_CriticalSection);
#ifdef REVERB_ENABLED
    if (m_fReverbActive != fReverb)
    {
        if (m_fReverbActive = fReverb)
        {
            if (!m_pCoefs)
            {
                long lSize = GetCoefsSize();
                m_pCoefs = (void *) malloc(lSize);
                lSize = GetStatesSize();
                m_pStates = (long *) malloc(lSize);
                if (m_pCoefs && m_pStates)
                {
                    memset((void *) m_pStates,0,lSize);
                    InitSVerb( (float) m_dwSampleRate, m_pCoefs);
                    InitSVerbStates( m_pStates );
			        SetReverb(&m_ReverbParams);
                }
            }
            else if (m_pStates) 
            {
                InitSVerbStates( m_pStates );
            }
        }
    }
#else
        m_fReverbActive = FALSE;
#endif
    ::LeaveCriticalSection(&m_CriticalSection);
}

BOOL CSynth::IsReverbActive()

{
    return m_fReverbActive;
}


HRESULT CSynth::Open(DWORD dwCableCount, DWORD dwVoices, BOOL fReverb)

{
	HRESULT hr = S_OK;
	if ((dwCableCount < 1) || (dwCableCount > MAX_CHANNEL_GROUPS))
	{
		return E_INVALIDARG;
	}
	if (m_ppControl)
	{
		return E_FAIL;	// Already opened.
	}
    ::EnterCriticalSection(&m_CriticalSection);
	hr = SetNumChannelGroups(dwCableCount);
	if (SUCCEEDED(hr))
	{
		short nTemp = (short) dwVoices / 4;
		if (nTemp < 4) nTemp = 4;
		SetMaxVoices((short) dwVoices, nTemp);
	}
    SetReverbActive(fReverb);
    m_vrGainAdjust = 0;
    ::LeaveCriticalSection(&m_CriticalSection);
	return hr;
}

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;
    m_fReverbActive = FALSE;
#ifdef REVERB_ENABLED
    if (m_pCoefs)
    {
        free(m_pCoefs);
        m_pCoefs = NULL;
    }
    if (m_pStates)
    {
        free(m_pStates);
        m_pStates = NULL;
    }
#endif
    ::LeaveCriticalSection(&m_CriticalSection);
	return S_OK;
}
   
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;
}

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;
}

HRESULT CSynth::Activate(DWORD dwSampleRate, DWORD dwChannels )

{
#ifdef REVERB_ENABLED
    if (m_fReverbActive && m_pStates && m_pCoefs)
    {
        InitSVerb( (float) m_dwSampleRate, m_pCoefs);
		InitSVerbStates( m_pStates );
        SetReverb(&m_ReverbParams);
	}
#endif
	m_stLastTime = 0;
	SetSampleRate(dwSampleRate);
	SetStereoMode(dwChannels);
	ResetPerformanceStats();
	return S_OK;
}

HRESULT CSynth::Deactivate()

{
	AllNotesOff();
	return S_OK;
}

HRESULT CSynth::GetPerformanceStats(PerfStats *pStats)

{
    if (pStats == NULL)
    {
        return E_POINTER;
    }
    *pStats = m_CopyStats;
    return (S_OK);
}

void CSynth::Mix(short *pBuffer,DWORD dwLength,LONGLONG llPosition)
{
    STIME stEndTime;
    CVoice *pVoice;
    CVoice *pNextVoice;
    long lNumVoices = 0;
    static BOOL fDidLast = FALSE;
    ::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);
	}
#ifdef REVERB_ENABLED
    if (m_fReverbActive && m_pCoefs && m_pStates)
    {
        if (m_dwStereo)
        {
            SVerbStereoToStereoShort(dwLength,pBuffer,pBuffer,m_pCoefs,m_pStates); 
        }
        else
        {
            SVerbMonoToMonoShort(dwLength,pBuffer,pBuffer,m_pCoefs,m_pStates); 
        }
    }
#endif
    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;
        memset(&m_BuildStats, 0, sizeof(m_BuildStats));
        m_stLastStats = m_stLastTime;
    }
    ::LeaveCriticalSection(&m_CriticalSection);
}

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;
}

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;
}

void CSynth::QueueVoice(CVoice *pVoice)

/*  This function 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.
*/

{
    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);
}

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;
            }
        }
    }
}


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;
}

HRESULT CSynth::Unload(HANDLE hDownload,
					   HRESULT ( CALLBACK *lpFreeMemory)(HANDLE,HANDLE),
					   HANDLE hUserData)
{
	return m_Instruments.Unload( hDownload, lpFreeMemory, hUserData);
}

HRESULT CSynth::Download(LPHANDLE phDownload, void * pdwData, LPBOOL bpFree) 

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

/* 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 IDirectMusicPort::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))
    {
#ifdef DBG
		if (stTime < m_stLastTime)
		{
			if ((lpBuffer[0] & 0xF0) == MIDI_NOTEON)
			{
				{
					Trace(2,"Note On at sample %ld, already mixed to %ld!\n",
						 stTime, m_stLastTime);
				}
			}
		}
#endif
		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;
}

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;
}

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;
}

HRESULT CSynth::AllNotesOff()

{
    CVoice *pVoice;
    ::EnterCriticalSection(&m_CriticalSection);
    while (pVoice = m_VoicesInUse.RemoveHead())
    {
        pVoice->ClearVoice();
        pVoice->m_fInUse = FALSE; 
        m_VoicesFree.AddHead(pVoice);
		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);
		}
    }
    ::LeaveCriticalSection(&m_CriticalSection);
    return (S_OK);
}

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;
}

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;
}


