//      Copyright (c) 1996-1999 Microsoft Corporation
//      Instrument.cpp
//

#ifdef DMSYNTH_MINIPORT
#include "common.h"
#else
#include "simple.h"
#include <mmsystem.h>
#include <dmerror.h>
#include "synth.h"
#include "math.h"
#include "validate.h"
#endif

void MemDump(char * prompt);

//#include <windowsx.h>

CSourceLFO::CSourceLFO()

{
    m_pfFrequency = 3804; // f = (256*4096*16*5hz)/(samplerate)
    m_stDelay = 0;
    m_prMWPitchScale = 0;
    m_vrMWVolumeScale = 0;
    m_vrVolumeScale = 0;
    m_prPitchScale = 0;
}

void CSourceLFO::Init(DWORD dwSampleRate)

{
    m_pfFrequency = (256 * 4096 * 16 * 5) / dwSampleRate;
    m_stDelay = 0;
    m_prMWPitchScale = 0;
    m_vrMWVolumeScale = 0;
    m_vrVolumeScale = 0;
    m_prPitchScale = 0;
}

void CSourceLFO::SetSampleRate(long lChange)

{
	if (lChange > 0)
	{
		m_stDelay <<= lChange;
		m_pfFrequency <<= lChange;
	}
	else
	{
		m_stDelay >>= -lChange;
		m_pfFrequency >>= -lChange;
	}
}

void CSourceLFO::Verify()

{
    FORCEBOUNDS(m_pfFrequency,64,7600);
    FORCEBOUNDS(m_stDelay,0,441000);
    FORCEBOUNDS(m_vrVolumeScale,-1200,1200);
    FORCEBOUNDS(m_vrMWVolumeScale,-1200,1200);
    FORCEBOUNDS(m_prPitchScale,-1200,1200);
    FORCEBOUNDS(m_prMWPitchScale,-1200,1200);
}

CSourceEG::CSourceEG()

{
	Init();
}

void CSourceEG::Init()

{
    m_stAttack = 0;
    m_stDecay = 0;
    m_pcSustain = 1000;
    m_stRelease = 0;
    m_trVelAttackScale = 0;
    m_trKeyDecayScale = 0;
    m_sScale = 0;
}

void CSourceEG::SetSampleRate(long lChange)

{
	if (lChange > 0)
	{
		m_stAttack <<= lChange;
		m_stDecay <<= lChange;
		m_stRelease <<= lChange;
	}
	else
	{
		m_stAttack >>= -lChange;
		m_stDecay >>= -lChange;
		m_stRelease >>= -lChange;
	}
}

void CSourceEG::Verify()

{
    FORCEBOUNDS(m_stAttack,0,1764000);
    FORCEBOUNDS(m_stDecay,0,1764000);
    FORCEBOUNDS(m_pcSustain,0,1000);
    FORCEBOUNDS(m_stRelease,0,1764000);
    FORCEBOUNDS(m_sScale,-1200,1200);
    FORCEBOUNDS(m_trKeyDecayScale,-12000,12000);
    FORCEBOUNDS(m_trVelAttackScale,-12000,12000);
}

CSourceArticulation::CSourceArticulation()

{
//    m_sVelToVolScale = -9600;
    m_wUsageCount = 0;
    m_sDefaultPan = 0;
	m_dwSampleRate = 22050;
	m_PitchEG.m_sScale = 0;	// pitch envelope defaults to off
}

void CSourceArticulation::Init(DWORD dwSampleRate)

{
    m_dwSampleRate = dwSampleRate;
    m_LFO.Init(dwSampleRate);       // Set to default values.
    m_PitchEG.Init();
    m_VolumeEG.Init();
}

void CSourceArticulation::SetSampleRate(DWORD dwSampleRate)

{
	if (dwSampleRate != m_dwSampleRate)
	{
		long lChange;
		if (dwSampleRate > (m_dwSampleRate * 2))
		{
			lChange = 2;		// going from 11 to 44.
		}
		else if (dwSampleRate > m_dwSampleRate)
		{
			lChange = 1;		// must be doubling
		}
		else if ((dwSampleRate * 2) < m_dwSampleRate)
		{
			lChange = -2;		// going from 44 to 11
		}
		else
		{
			lChange = -1;		// that leaves halving.
		}
		m_dwSampleRate = dwSampleRate;
		m_LFO.SetSampleRate(lChange);
		m_PitchEG.SetSampleRate(lChange);
		m_VolumeEG.SetSampleRate(lChange);
	}
}

void CSourceArticulation::Verify()

{
    m_LFO.Verify();
    m_PitchEG.Verify();
    m_VolumeEG.Verify();
}

void CSourceArticulation::AddRef()

{
    m_wUsageCount++;
}

void CSourceArticulation::Release()

{
    m_wUsageCount--;
    if (m_wUsageCount == 0)
    {
        delete this;
    }
}

CSourceSample::CSourceSample()

{
    m_pWave = NULL;
    m_dwLoopStart = 0;
    m_dwLoopEnd = 1;
    m_dwSampleLength = 0;
    m_prFineTune = 0;
    m_dwSampleRate = 22050;
    m_bMIDIRootKey = 60;
    m_bOneShot = TRUE;
    m_bSampleType = 0;
}

CSourceSample::~CSourceSample()

{ 
	if (m_pWave != NULL)
	{
		m_pWave->Release();
	}
}

void CSourceSample::Verify()

{
    if (m_pWave != NULL)
    {
        FORCEBOUNDS(m_dwSampleLength,0,m_pWave->m_dwSampleLength);
        FORCEBOUNDS(m_dwLoopEnd,1,m_dwSampleLength);
        FORCEBOUNDS(m_dwLoopStart,0,m_dwLoopEnd);
        if ((m_dwLoopEnd - m_dwLoopStart) < 6) 
        {
            m_bOneShot = TRUE;
        }
    }
    FORCEBOUNDS(m_dwSampleRate,3000,80000);
    FORCEBOUNDS(m_bMIDIRootKey,0,127);
    FORCEBOUNDS(m_prFineTune,-1200,1200);
}

BOOL CSourceSample::CopyFromWave()

{
	if (m_pWave == NULL)
	{
		return FALSE;
	}
	m_dwSampleLength = m_pWave->m_dwSampleLength;
	m_dwSampleRate = m_pWave->m_dwSampleRate;
	m_bSampleType = m_pWave->m_bSampleType; 
	if (m_bOneShot)
	{
		m_dwSampleLength--;
		if (m_pWave->m_bSampleType & SFORMAT_16)
		{
			m_pWave->m_pnWave[m_dwSampleLength] = 0;
		}
		else
		{
			char *pBuffer = (char *) m_pWave->m_pnWave;
			pBuffer[m_dwSampleLength] = 0;
		}
	}
	else 
	{
		if (m_dwLoopStart >= m_dwSampleLength)
		{
			m_dwLoopStart = 0;
		}
		if (m_pWave->m_bSampleType & SFORMAT_16)
		{
			m_pWave->m_pnWave[m_dwSampleLength-1] =
				m_pWave->m_pnWave[m_dwLoopStart];
		}
		else
		{
			char *pBuffer = (char *) m_pWave->m_pnWave;
			pBuffer[m_dwSampleLength-1] =
				pBuffer[m_dwLoopStart];
		}
	}
	Verify();
	return (TRUE);
}


CWave::CWave()
{
	m_hUserData = NULL;
	m_lpFreeHandle = NULL;
    m_pnWave = NULL;
    m_dwSampleRate = 22050;
    m_bSampleType = SFORMAT_16;
    m_dwSampleLength = 0;
    m_wUsageCount = 0;
    m_dwID = 0;
	m_wPlayCount = 0;
}

CWave::~CWave()

{
	if (m_pnWave && m_lpFreeHandle)
	{
		m_lpFreeHandle((HANDLE) this,m_hUserData);
	}
}

void CWave::Verify()

{
    FORCEBOUNDS(m_dwSampleRate,3000,80000);
}

void CWave::PlayOn()

{
	m_wPlayCount++;
	AddRef();
}

void CWave::PlayOff()

{
	m_wPlayCount--;
	Release();
}

BOOL CWave::IsPlaying()

{
	return (m_wPlayCount);
}

void CWave::AddRef()

{
    m_wUsageCount++;
}

void CWave::Release()

{
    m_wUsageCount--;
    if (m_wUsageCount == 0)
    {
        delete this;
    }
}

CSourceRegion::CSourceRegion()
{
    m_pArticulation = NULL;
    m_vrAttenuation = 0;
    m_prTuning = 0;
    m_bKeyHigh = 127;
    m_bKeyLow = 0;
    m_bGroup = 0;
    m_bAllowOverlap = FALSE;
}

CSourceRegion::~CSourceRegion()
{
    if (m_pArticulation)
    {
        m_pArticulation->Release();
    }
}

void CSourceRegion::SetSampleRate(DWORD dwSampleRate)

{
	if (m_pArticulation != NULL)
	{
		m_pArticulation->SetSampleRate(dwSampleRate);
	}
}

void CSourceRegion::Verify()

{
    FORCEBOUNDS(m_bKeyHigh,0,127);
    FORCEBOUNDS(m_bKeyLow,0,127);
    FORCEBOUNDS(m_prTuning,-12000,12000);
    FORCEBOUNDS(m_vrAttenuation,-9600,0);
    m_Sample.Verify();
    if (m_pArticulation != NULL)
    {
        m_pArticulation->Verify();
    }
}

CInstrument::CInstrument()
{
    m_dwProgram = 0;
}

CInstrument::~CInstrument()
{
	while (!m_RegionList.IsEmpty())
	{
        CSourceRegion *pRegion = m_RegionList.RemoveHead();
        delete pRegion;
	}
}

void CInstrument::Verify()

{
    CSourceRegion *pRegion = m_RegionList.GetHead();
    CSourceArticulation *pArticulation = NULL;
    for (;pRegion != NULL;pRegion = pRegion->GetNext())
    {
        if (pRegion->m_pArticulation != NULL)
        {
            pArticulation = pRegion->m_pArticulation;
        }
        pRegion->Verify();
    }
    pRegion = m_RegionList.GetHead();
    for (;pRegion != NULL;pRegion = pRegion->GetNext())
    {
        if (pRegion->m_pArticulation == NULL)
        {
            pRegion->m_pArticulation = pArticulation;
            pArticulation->AddRef();
        }
    }
}

void CInstrument::SetSampleRate(DWORD dwSampleRate)

{
    CSourceRegion *pRegion = m_RegionList.GetHead();
    for (;pRegion;pRegion = pRegion->GetNext())
    {
		pRegion->SetSampleRate(dwSampleRate);
	}
}

CSourceRegion * CInstrument::ScanForRegion(DWORD dwNoteValue)

{
    CSourceRegion *pRegion = m_RegionList.GetHead();
    for (;pRegion;pRegion = pRegion->GetNext())
    {
        if (dwNoteValue >= pRegion->m_bKeyLow &&
            dwNoteValue <= pRegion->m_bKeyHigh)
        {
            break ;
        }
    }
    return pRegion;
}

void CInstManager::SetSampleRate(DWORD dwSampleRate)

{
    DWORD dwIndex;
    m_dwSampleRate = dwSampleRate;
	EnterCriticalSection(&m_CriticalSection);
    for (dwIndex = 0; dwIndex < INSTRUMENT_HASH_SIZE; dwIndex++)
    {
	    CInstrument *pInstrument = m_InstrumentList[dwIndex].GetHead();
	    for (;pInstrument != NULL; pInstrument = pInstrument->GetNext())
	    {
		    pInstrument->SetSampleRate(dwSampleRate);
	    }
    }
	LeaveCriticalSection(&m_CriticalSection);
}

CInstManager::CInstManager()

{
	m_dwSampleRate = 22050;
    m_fCSInitialized = FALSE;
    InitializeCriticalSection(&m_CriticalSection);
    m_fCSInitialized = TRUE;
}

CInstManager::~CInstManager()

{
    if (m_fCSInitialized)
    {
        DWORD dwIndex;
        for (dwIndex = 0; dwIndex < INSTRUMENT_HASH_SIZE; dwIndex++)
        {
    	    while (!m_InstrumentList[dwIndex].IsEmpty())
    	    {
                CInstrument *pInstrument = m_InstrumentList[dwIndex].RemoveHead();
                delete pInstrument;
    	    }
        }
        for (dwIndex = 0; dwIndex < WAVE_HASH_SIZE; dwIndex++)
        {
    	    while (!m_WavePool[dwIndex].IsEmpty()) 
    	    {
                CWave *pWave = m_WavePool[dwIndex].RemoveHead();
                pWave->Release();
    	    }
        }
    	while (!m_FreeWavePool.IsEmpty()) 
    	{
            CWave *pWave = m_FreeWavePool.RemoveHead();
            pWave->Release();
    	}
        DeleteCriticalSection(&m_CriticalSection);
    }
}

void CInstManager::Verify()

{
    DWORD dwIndex;
    EnterCriticalSection(&m_CriticalSection);
    for (dwIndex = 0;dwIndex < INSTRUMENT_HASH_SIZE; dwIndex++)
    {
        CInstrument *pInstrument = m_InstrumentList[dwIndex].GetHead();
        for (;pInstrument != NULL;pInstrument = pInstrument->GetNext())
        {
            pInstrument->Verify();
        }
    }
    LeaveCriticalSection(&m_CriticalSection);
}

CInstrument * CInstManager::GetInstrument(DWORD dwProgram, 
                                        DWORD dwKey)

{
    EnterCriticalSection(&m_CriticalSection);
    CInstrument *pInstrument = m_InstrumentList[dwProgram % INSTRUMENT_HASH_SIZE].GetHead();
    for (;pInstrument != NULL; pInstrument = pInstrument->GetNext())
    {
        if (pInstrument->m_dwProgram == dwProgram) 
        {
            if (pInstrument->ScanForRegion(dwKey) != NULL)
            {
                break;
            }
            else
            {
                Trace(1,"No region was found in instrument # %lx that matched note %ld\n",
                    dwProgram,dwKey);
            }
        }
    }
    LeaveCriticalSection(&m_CriticalSection);
    return (pInstrument);
}


DWORD TimeCents2Samples(long tcTime, DWORD dwSampleRate)
{
    if (tcTime ==  0x80000000) return (0);
    double flTemp = tcTime;
    flTemp /= (65536 * 1200);
    flTemp = pow(2.0,flTemp);
    flTemp *= dwSampleRate;
    return (DWORD) flTemp;
}

DWORD PitchCents2PitchFract(long pcRate,DWORD dwSampleRate)

{
    double fTemp = pcRate;
    fTemp /= 65536;
    fTemp -= 6900;
    fTemp /= 1200;
    fTemp = pow(2.0,fTemp);
    fTemp *= 7381975040.0; // (440*256*16*4096);
    fTemp /= dwSampleRate;
    return (DWORD) (fTemp);
}

HRESULT CSourceArticulation::Download(DMUS_DOWNLOADINFO * pInfo, 
								void * pvOffsetTable[], 
								DWORD dwIndex, 
								DWORD dwSampleRate,
                                BOOL fNewFormat)
{
    // Depending on whether this is the new DX7 format, we either are reading a fixed set of parameters
    // or parsing an articulation chunk directly copied from the DLS file. The latter is obviously more
    // flexible and it turns out to make much more sense once we get to DLS2.
    if (fNewFormat)
    {
        DMUS_ARTICULATION2 * pdmArtic = 
            (DMUS_ARTICULATION2 *) pvOffsetTable[dwIndex];
   	    while (pdmArtic)
        {
            if (pdmArtic->ulArtIdx)
            {
                if (pdmArtic->ulArtIdx >= pInfo->dwNumOffsetTableEntries)
		        {
			        return DMUS_E_BADARTICULATION;
		        }   	    
                DWORD dwPosition;
                void *pData = pvOffsetTable[pdmArtic->ulArtIdx];
                CONNECTIONLIST * pConnectionList = 
                    (CONNECTIONLIST *) pData;
                CONNECTION *pConnection;
                dwPosition = sizeof(CONNECTIONLIST);
		        for (dwIndex = 0; dwIndex < pConnectionList->cConnections; dwIndex++)
		        {
                    pConnection = (CONNECTION *) ((BYTE *)pData + dwPosition);
                    dwPosition += sizeof(CONNECTION);
			        switch (pConnection->usSource)
			        {
			        case CONN_SRC_NONE :
				        switch (pConnection->usDestination)
				        {
				        case CONN_DST_LFO_FREQUENCY :
                            m_LFO.m_pfFrequency = PitchCents2PitchFract(
		                        pConnection->lScale,dwSampleRate);
					        break;
				        case CONN_DST_LFO_STARTDELAY :
	                        m_LFO.m_stDelay = TimeCents2Samples(
		                        (TCENT) pConnection->lScale,dwSampleRate);
					        break;
				        case CONN_DST_EG1_ATTACKTIME :
                            m_VolumeEG.m_stAttack = TimeCents2Samples(
		                        (TCENT) pConnection->lScale,dwSampleRate);
					        break;
				        case CONN_DST_EG1_DECAYTIME :
                            m_VolumeEG.m_stDecay = TimeCents2Samples(
		                    (TCENT) pConnection->lScale,dwSampleRate);
					        break;
				        case CONN_DST_EG1_SUSTAINLEVEL :
                            m_VolumeEG.m_pcSustain = 
		                        (SPERCENT) ((long) (pConnection->lScale >> 16));
					        break;
				        case CONN_DST_EG1_RELEASETIME :
                            m_VolumeEG.m_stRelease = TimeCents2Samples(
		                        (TCENT) pConnection->lScale,dwSampleRate);
					        break;
				        case CONN_DST_EG2_ATTACKTIME :
                            m_PitchEG.m_stAttack = TimeCents2Samples(
		                        (TCENT) pConnection->lScale,dwSampleRate);
					        break;
				        case CONN_DST_EG2_DECAYTIME :
                            m_PitchEG.m_stDecay = TimeCents2Samples(
		                        (TCENT) pConnection->lScale,dwSampleRate);
					        break;
				        case CONN_DST_EG2_SUSTAINLEVEL :
                            m_PitchEG.m_pcSustain =
		                        (SPERCENT) ((long) (pConnection->lScale >> 16));
					        break;
				        case CONN_DST_EG2_RELEASETIME :
              	            m_PitchEG.m_stRelease = TimeCents2Samples(
		                        (TCENT) pConnection->lScale,dwSampleRate); 
					        break;
				        case CONN_DST_PAN :
                            m_sDefaultPan = (short) 
		                        ((long) ((long) pConnection->lScale >> 12) / 125);
					        break;
				        }
				        break;
			        case CONN_SRC_LFO :
				        switch (pConnection->usControl)
				        {
				        case CONN_SRC_NONE :
					        switch (pConnection->usDestination)
					        {
					        case CONN_DST_ATTENUATION :
                                m_LFO.m_vrVolumeScale = (VRELS)
		                            ((long) ((pConnection->lScale * 10) >> 16));
						        break;
					        case CONN_DST_PITCH :
                                m_LFO.m_prPitchScale = (PRELS)
		                            ((long) (pConnection->lScale >> 16)); 
						        break;
					        }
					        break;
				        case CONN_SRC_CC1 :
					        switch (pConnection->usDestination)
					        {
					        case CONN_DST_ATTENUATION :
                                m_LFO.m_vrMWVolumeScale = (VRELS)
                		            ((long) ((pConnection->lScale * 10) >> 16)); 
						        break;
					        case CONN_DST_PITCH :
                                m_LFO.m_prMWPitchScale = (PRELS)
                		            ((long) (pConnection->lScale >> 16)); 
						        break;
					        }
					        break;
				        }
				        break;
			        case CONN_SRC_KEYONVELOCITY :
				        switch (pConnection->usDestination)
				        {
				        case CONN_DST_EG1_ATTACKTIME :
                            m_VolumeEG.m_trVelAttackScale = (TRELS)
            		            ((long) (pConnection->lScale >> 16));
					        break;
				        case CONN_DST_EG2_ATTACKTIME :
                            m_PitchEG.m_trVelAttackScale = (TRELS)
            		            ((long) (pConnection->lScale >> 16));
					        break;
				        case CONN_DST_ATTENUATION :
					        break;
				        }
				        break;
			        case CONN_SRC_KEYNUMBER :
				        switch (pConnection->usDestination)
				        {
				        case CONN_DST_EG1_DECAYTIME :
                            m_VolumeEG.m_trKeyDecayScale = (TRELS)
            		            ((long) (pConnection->lScale >> 16));
					        break;
				        case CONN_DST_EG2_DECAYTIME :
                            m_PitchEG.m_trKeyDecayScale = (TRELS)
            		            ((long) (pConnection->lScale >> 16));
					        break;
				        }
				        break;
			        case CONN_SRC_EG2 :
				        switch (pConnection->usDestination)
				        {
				        case CONN_DST_PITCH :
                            m_PitchEG.m_sScale = (short)
		                        ((long) (pConnection->lScale >> 16));
					        break;
				        }
				        break;		
    		        }
		        }
            }
            if (pdmArtic->ulNextArtIdx)
            {
                if (pdmArtic->ulNextArtIdx >= pInfo->dwNumOffsetTableEntries)
		        {
			        return DMUS_E_BADARTICULATION;
		        }
                pdmArtic = (DMUS_ARTICULATION2 *) pvOffsetTable[pdmArtic->ulNextArtIdx];
            }
            else 
            {
                pdmArtic = NULL;
            }
        }
    }
    else
    {
        DMUS_ARTICULATION * pdmArtic = 
            (DMUS_ARTICULATION *) pvOffsetTable[dwIndex];

	    if (pdmArtic->ulArt1Idx)
        {
            if (pdmArtic->ulArt1Idx >= pInfo->dwNumOffsetTableEntries)
		    {
			    return DMUS_E_BADARTICULATION;
		    }
    	    DMUS_ARTICPARAMS * pdmArticParams = 
                (DMUS_ARTICPARAMS *) pvOffsetTable[pdmArtic->ulArt1Idx];

            m_LFO.m_pfFrequency = PitchCents2PitchFract(
		        pdmArticParams->LFO.pcFrequency,dwSampleRate);
	        m_LFO.m_stDelay = TimeCents2Samples(
		        (TCENT) pdmArticParams->LFO.tcDelay,dwSampleRate);
	        m_LFO.m_vrVolumeScale = (VRELS)
		        ((long) ((pdmArticParams->LFO.gcVolumeScale * 10) >> 16));
	        m_LFO.m_prPitchScale = (PRELS)
		        ((long) (pdmArticParams->LFO.pcPitchScale >> 16)); 
	        m_LFO.m_vrMWVolumeScale = (VRELS)
		        ((long) ((pdmArticParams->LFO.gcMWToVolume * 10) >> 16)); 
	        m_LFO.m_prMWPitchScale = (PRELS)
		        ((long) (pdmArticParams->LFO.pcMWToPitch >> 16)); 

	        m_VolumeEG.m_stAttack = TimeCents2Samples(
		        (TCENT) pdmArticParams->VolEG.tcAttack,dwSampleRate);
	        m_VolumeEG.m_stDecay = TimeCents2Samples(
		        (TCENT) pdmArticParams->VolEG.tcDecay,dwSampleRate);
	        m_VolumeEG.m_pcSustain = 
		        (SPERCENT) ((long) (pdmArticParams->VolEG.ptSustain >> 16));
	        m_VolumeEG.m_stRelease = TimeCents2Samples(
		        (TCENT) pdmArticParams->VolEG.tcRelease,dwSampleRate); 
	        m_VolumeEG.m_trVelAttackScale = (TRELS)
		        ((long) (pdmArticParams->VolEG.tcVel2Attack >> 16));
	        m_VolumeEG.m_trKeyDecayScale = (TRELS)
		        ((long) (pdmArticParams->VolEG.tcKey2Decay >> 16));

	        m_PitchEG.m_trKeyDecayScale = (TRELS)
		        ((long) (pdmArticParams->PitchEG.tcKey2Decay >> 16));
	        m_PitchEG.m_sScale = (short)
		        ((long) (pdmArticParams->PitchEG.pcRange >> 16));
	        m_PitchEG.m_trVelAttackScale = (TRELS)
		        ((long) (pdmArticParams->PitchEG.tcVel2Attack >> 16));
	        m_PitchEG.m_stAttack = TimeCents2Samples(
		        (TCENT) pdmArticParams->PitchEG.tcAttack,dwSampleRate);
	        m_PitchEG.m_stDecay = TimeCents2Samples(
		        (TCENT) pdmArticParams->PitchEG.tcDecay,dwSampleRate);
	        m_PitchEG.m_pcSustain =
		        (SPERCENT) ((long) (pdmArticParams->PitchEG.ptSustain >> 16));
	        m_PitchEG.m_stRelease = TimeCents2Samples(
		        (TCENT) pdmArticParams->PitchEG.tcRelease,dwSampleRate); 

	        m_sDefaultPan = (short) 
		        ((long) ((long) pdmArticParams->Misc.ptDefaultPan >> 12) / 125);
        }
    }
	Verify();	// Make sure all parameters are legal.

	return S_OK;
}

/* Download Region
   Parse through the region chunks, including any embedded articulation.
*/    

HRESULT CSourceRegion::Download(DMUS_DOWNLOADINFO * pInfo,  // DMUS_DOWNLOADINFO header structure.
								void * pvOffsetTable[],     // Offset table with addresses of all embedded structures.
								DWORD *pdwRegionIX,         // Region index for first region.
								DWORD dwSampleRate,         // Sample rate, used to convert time parameters.
                                BOOL fNewFormat)            // Is this the DMUS_DOWNLOADINFO_INSTRUMENT2 format?

{
	DMUS_REGION * pdmRegion = (DMUS_REGION *) pvOffsetTable[*pdwRegionIX];
	*pdwRegionIX = pdmRegion->ulNextRegionIdx;	// Clear to avoid loops.
	pdmRegion->ulNextRegionIdx = 0;
	// Read the Region chunk...
	m_bKeyHigh = (BYTE) pdmRegion->RangeKey.usHigh;
    m_bKeyLow = (BYTE) pdmRegion->RangeKey.usLow;
    if (pdmRegion->fusOptions & F_RGN_OPTION_SELFNONEXCLUSIVE)
	{
		m_bAllowOverlap = TRUE;
	}
	else
	{
		m_bAllowOverlap = FALSE;
	}
    m_bGroup = (BYTE) pdmRegion->usKeyGroup;
	// Now, the WSMP and WLOOP chunks...
    m_vrAttenuation = (short) ((long) ((pdmRegion->WSMP.lAttenuation) * 10) >> 16);
    m_Sample.m_prFineTune = pdmRegion->WSMP.sFineTune;
    m_Sample.m_bMIDIRootKey = (BYTE) pdmRegion->WSMP.usUnityNote;
    if (pdmRegion->WSMP.cSampleLoops == 0)
    {
        m_Sample.m_bOneShot = TRUE;
    }
    else
    {
        m_Sample.m_dwLoopStart = pdmRegion->WLOOP[0].ulStart;
        m_Sample.m_dwLoopEnd = m_Sample.m_dwLoopStart + pdmRegion->WLOOP[0].ulLength;
        m_Sample.m_bOneShot = FALSE;
    }
	m_Sample.m_dwSampleRate = dwSampleRate;
	// Then the WAVELINK...
    if (pdmRegion->WaveLink.ulChannel != WAVELINK_CHANNEL_LEFT)
    {
        return DMUS_E_NOTMONO;
    }
    m_Sample.m_dwID = (DWORD) pdmRegion->WaveLink.ulTableIndex;
	// Does it have its own articulation?
	if (pdmRegion->ulRegionArtIdx )
	{
		if (pdmRegion->ulRegionArtIdx >= pInfo->dwNumOffsetTableEntries)
		{
			return DMUS_E_BADARTICULATION;
		}

        CSourceArticulation *pArticulation = new CSourceArticulation;
        if (pArticulation)
		{
            pArticulation->Init(dwSampleRate);
            HRESULT hr = pArticulation->Download(pInfo, pvOffsetTable, 
                    pdmRegion->ulRegionArtIdx, dwSampleRate, fNewFormat);

			if (FAILED(hr))
			{
				delete pArticulation;
				return hr;
			}
			m_pArticulation = pArticulation;
			m_pArticulation->AddRef();
		}
		else
		{
			return E_OUTOFMEMORY;
		}
	}
	return S_OK;
}

/* DownloadInstrument
   This is called by Download() when an instrument chunk is encountered. 
   Using the offset table to resolve linkages, it scans through the instrument,
   parsing out regions and articulations, then finally linking the regions in the
   instrument to the waves, which should have been previously downloaded.
*/

HRESULT CInstManager::DownloadInstrument(LPHANDLE phDownload,       // Pointer to download handle, to be created by synth.
										 DMUS_DOWNLOADINFO *pInfo,  // DMUS_DOWNLOADINFO header structure.
										 void *pvOffsetTable[],     // Offset table with addresses of all embedded structures.
										 void *pvData,              // Pointer to start of download data.
                                         BOOL fNewFormat)           // If true, this is a DMUS_DOWNLOADINFO_INSTRUMENT2 format download.
										 
{
    // The download data must start with the DMUS_INSTRUMENT chunk, so cast to that.
	DMUS_INSTRUMENT *pdmInstrument = (DMUS_INSTRUMENT *) pvData;
    // Create a new CInstrument structure. This stores everything that describes an instrument, including
    // the articulations and regions.
	CInstrument *pInstrument = new CInstrument;
	if (pInstrument)
	{
        // For debugging purposes, print a trace statement to show that the instrument has actually been downloaded.
        // This only occurs in debug builds and will only print if dmsynth=1 is set in [debug] section in win.ini.
        Trace(1,"Downloading instrument %lx\n",pdmInstrument->ulPatch);
		pInstrument->m_dwProgram = pdmInstrument->ulPatch;
        // Start by scanning through the regions.
		DWORD dwRegionIX = pdmInstrument->ulFirstRegionIdx;
		pdmInstrument->ulFirstRegionIdx = 0; // Clear to avoid loops.
		while (dwRegionIX)
		{
            // For each region, verify that the index number is actually legal.
			if (dwRegionIX >= pInfo->dwNumOffsetTableEntries)
			{
				delete pInstrument;
				return DMUS_E_BADINSTRUMENT;
			}
			CSourceRegion *pRegion = new CSourceRegion;
			if (!pRegion)
			{
				delete pInstrument;
				return E_OUTOFMEMORY;
			}
			pInstrument->m_RegionList.AddHead(pRegion);
            // Call the region's Download method to parse the region structure and optional embedded articulation.
			HRESULT hr = pRegion->Download(pInfo, pvOffsetTable, &dwRegionIX, m_dwSampleRate, fNewFormat);
			if (FAILED(hr))
			{
				delete pInstrument;
				return hr;
			}
			EnterCriticalSection(&m_CriticalSection);
            // Once the region is parsed, we need to connect it to the wave, that should have been
            // previously downloaded into the wavepool. 
            // Because of the hash table, the linked list is never very long, so the search is quick.
			CWave *pWave = m_WavePool[pRegion->m_Sample.m_dwID % WAVE_HASH_SIZE].GetHead();
			for (;pWave;pWave = pWave->GetNext())
			{
                // Each wave has a unique ID, which the regions match up with.
				if (pRegion->m_Sample.m_dwID == pWave->m_dwID)
				{
					pRegion->m_Sample.m_pWave = pWave;
					pWave->AddRef();
					pRegion->m_Sample.CopyFromWave();
					break;
				}
			}
			LeaveCriticalSection(&m_CriticalSection);
		}
        // Once all the regions are loaded, see if we have a global articulation.
		if (pdmInstrument->ulGlobalArtIdx)
		{
            // If so load it. First check that it's a valid index.
			if (pdmInstrument->ulGlobalArtIdx >= pInfo->dwNumOffsetTableEntries)
			{
				delete pInstrument;
				return DMUS_E_BADARTICULATION;
			}
            // Create an articulation and have it parse the data.
            CSourceArticulation *pArticulation = new CSourceArticulation;
            if (pArticulation)
			{
                // The articulation will convert all time parameters into sample times, so it needs to know the sample rate.
                pArticulation->Init(m_dwSampleRate);
                // Parse the articulation data. Note that the fNewFormat flag indicates whether this is in the DX6 fixed
                // format articulation or the DX7 dynamic format (which is actually the same as the file format.)
                HRESULT hr = pArticulation->Download(pInfo, pvOffsetTable, 
                        pdmInstrument->ulGlobalArtIdx, m_dwSampleRate, fNewFormat);
				if (FAILED(hr))
				{
					delete pArticulation;
					return hr;
				}
                // Once the global articulation is read, scan all regions and assign the articulation to all
                // regions that don't have articulations yet.
				for (CSourceRegion *pr = pInstrument->m_RegionList.GetHead();
					 pr != NULL;
					 pr = pr->GetNext())
				{
					if (pr->m_pArticulation == NULL)
					{
						pr->m_pArticulation = pArticulation;
						pArticulation->AddRef();    
					}
				}
                if (!pArticulation->m_wUsageCount)
                {
                    delete pArticulation;
                }
			}
			else
			{
				return E_OUTOFMEMORY;
			}
		}
		else
		{
			for (CSourceRegion *pr = pInstrument->m_RegionList.GetHead();
				 pr != NULL;
				 pr = pr->GetNext())
			{
				if (pr->m_pArticulation == NULL)
				{
					delete pInstrument;
					return DMUS_E_NOARTICULATION;
				}
			}
		}
		EnterCriticalSection(&m_CriticalSection);
        // If this is a GM instrument, make sure that it will be searched for last by placing it at
        // the end of the list. The DLS spec states that
        // a DLS collection with the same patch as a GM instrument will always override the GM instrument.
		if (pdmInstrument->ulFlags & DMUS_INSTRUMENT_GM_INSTRUMENT)
		{
			pInstrument->SetNext(NULL);
			m_InstrumentList[pInstrument->m_dwProgram % INSTRUMENT_HASH_SIZE].AddTail(pInstrument);
		}
		else
		{
			m_InstrumentList[pInstrument->m_dwProgram % INSTRUMENT_HASH_SIZE].AddHead(pInstrument);
		}
		LeaveCriticalSection(&m_CriticalSection);
		*phDownload = (HANDLE) pInstrument;
		return S_OK;
	}
	return E_OUTOFMEMORY;
}

/* DownloadWave
   This is called by Download when it receives a wave download chunk.
   DownloadWave parses the wave, converts the wave data if necessary, and
   places the wave in the wave pool, where it can subsequentley be 
   connected to instruments. (All the waves referenced by an instrument
   are downloaded prior to downloading the instrument itself. This makes the
   whole process simpler and more reliable. Conversely, on unload, all 
   waves are unloaded after the instruments that reference them.)
*/

HRESULT CInstManager::DownloadWave(LPHANDLE phDownload,         // Download handle, to be returned. This will be used by a later Unload call to reference the wave.
								   DMUS_DOWNLOADINFO *pInfo,    // DMUS_DOWNLOADINFO structure from the head of the download chunk. This provides the total size of data, among other things.
								   void *pvOffsetTable[],       // The table of offsets in the download data.
								   void *pvData)                // Finally, the data itself.
{
	DMUS_WAVE *pdmWave = (DMUS_WAVE *) pvData;  // The start of the data should align with a DMUS_WAVE header.
    // Make sure that the wave data is properly uncompressed PCM data.
	if (pdmWave->WaveformatEx.wFormatTag != WAVE_FORMAT_PCM)
    {
        return DMUS_E_NOTPCM;
    }
    // The data can only be mono format.
    if (pdmWave->WaveformatEx.nChannels != 1)
    {
        return DMUS_E_NOTMONO;
    }
    // Ensure that the index to the wave data is a legal value in the offset table. 
	if (pdmWave->ulWaveDataIdx >= pInfo->dwNumOffsetTableEntries)
	{
		return DMUS_E_BADWAVE;
	}
    // Create a wave object and parse the data into it.
	CWave *pWave = new CWave;
	if (pWave)
	{	
        // We've already verified that the wave data index is a valid index, so go ahead
        // and use the offset table to convert that into a valid DMUS_WAVEDATA structure pointer. 
		DMUS_WAVEDATA *pdmWaveData= (DMUS_WAVEDATA *) 
			pvOffsetTable[pdmWave->ulWaveDataIdx];
        Trace(3,"Downloading wave %ld\n",pInfo->dwDLId);
        // Now initialize the CWave structure.
		pWave->m_dwID = pInfo->dwDLId;
		pWave->m_hUserData = NULL;
		pWave->m_lpFreeHandle = NULL;
		pWave->m_dwSampleLength = pdmWaveData->cbSize;
		pWave->m_pnWave = (short *) &pdmWaveData->byData[0];
        pWave->m_dwSampleRate = pdmWave->WaveformatEx.nSamplesPerSec;
        // If the wave data is 8 bit, the data needs to be converted to
        // two's complement representation.
        if (pdmWave->WaveformatEx.wBitsPerSample == 8)
        {
            pWave->m_bSampleType = SFORMAT_8;
			DWORD dwX;
			char *pData = (char *) &pdmWaveData->byData[0];
			for (dwX = 0; dwX < pWave->m_dwSampleLength; dwX++)
			{
				pData[dwX] -= (char) 128;
			}
        }
        else if (pdmWave->WaveformatEx.wBitsPerSample == 16)
        {
			pWave->m_dwSampleLength >>= 1;
            pWave->m_bSampleType = SFORMAT_16;
        }
        else
        {
			delete pWave;
            return DMUS_E_BADWAVE;
        }
        pWave->m_dwSampleLength++;  // We always add one sample to the end for interpolation.
		EnterCriticalSection(&m_CriticalSection);
        // Place the wave in a hash table of wave lists to increase access speed.
		m_WavePool[pWave->m_dwID % WAVE_HASH_SIZE].AddHead(pWave);
		LeaveCriticalSection(&m_CriticalSection);
        // Return the pointer to the internal CWave object as the handle. This will
        // be used in a subsequant call to unload the wave object.
		*phDownload = (HANDLE) pWave;
		pWave->AddRef();
		return S_OK;
	}
	return E_OUTOFMEMORY;
}

/* Download
   This is the heart of the DLS download mechanism, and is called from IDirectMusicSynth::Download().
   It verifies the offset table and converts it into physical addresses. Then, 
   depending on whether this is a wave or instrument download, it calls the 
   appropriate method.
   
   The data is stored in a continuous chunk of memory,
   pointed to by pvData. However, at the head of the chunk are two data 
   structures, which define the nature of the data to follow. These are 
   the DMUS_DOWNLOADINFO and DMUS_OFFSETTABLE structures. DMUS_DOWNLOADINFO
   is a header which describes how to parse the data, including
   its size and intention (wave or instrument.) DMUS_OFFSETTABLE provides 
   a set of indexes into the data segment which follows. All parsing through the data
   is managed through this table. Whenever a structure in the data references 
   another structure, it describes it by an index into the offset table. 
   The offset table then converts it into a physical address in the memory. 
   This allows the synthesizer to do bounds checking on all
   references, making the implementation more robust. In kernel mode 
   implementations, the driver can make its own private copy of the offset 
   table, and so ensure that an application in user mode can not mess with 
   its referencing and cause a crash. This implementation also makes a unique copy.
   
   Looking closer at DMUS_DOWNLOADINFO, DMUS_DOWNLOADINFO.dwDLType 
   determines the type of data being downloaded. It is set to 
   DMUS_DOWNLOADINFO_INSTRUMENT or DMUS_DOWNLOADINFO_INSTRUMENT2
   for an instrument, DMUS_DOWNLOADINFO_WAVE for a wave. As new data types emerge,
   identifiers will be allocated for them.
   DMUS_DOWNLOADINFO.dwDLId holds a unique 32 bit identifier for the object. 
   This identifier is used to connect objects together. For example, it is used
   to connect waves to instruments. 
   DMUS_DOWNLOADINFO.dwNumOffsetTableEntries describes the number of entries in 
   the DMUS_OFFSETTABLE structure, which follows. 
   Finally, DMUS_DOWNLOADINFO.cbSizeData states the total size of the 
   memory chunk, which follows the offset table.

   Depending on the synthesizer implementation, it may decide to use the memory 
   in the download chunk. This reduces memory allocation and freeing, since, if enough 
   memory has been allocated to store a wave, that same memory can be used by 
   the synthesizer to store it for playback. So, the synthesizer has the option 
   of hanging on to the memory, returning its decision in the pbFree parameter. 
   If it does keep the memory, then the caller must not free it. Later, the 
   IDirectMusicSynth::Unload command has a callback mechanism to handle asynchronous 
   freeing of the memory once the unload request has been made.
*/

HRESULT CInstManager::Download(LPHANDLE phDownload, // Download handle, to be returned.
							   void * pvData,       // Pointer to download data chunk.
							   LPBOOL pbFree)       // Pointer to boolean to indicate whether the data chunk can be freed now or held until unload.
							   

{
	V_INAME(IDirectMusicSynth::Download);
	V_BUFPTR_READ(pvData,sizeof(DMUS_DOWNLOADINFO));

    HRESULT hr = DMUS_E_UNKNOWNDOWNLOAD;
    // We need an array of pointers to reproduce the offset table, which is used to link to
    // specific structures in the download chunk. 
    void ** ppvOffsetTable;	
    // At the head of the download chunk is the download header, in the form of a DMUS_DOWNLOADINFO structure.
	DMUS_DOWNLOADINFO * pInfo = (DMUS_DOWNLOADINFO *) pvData; // Cast to the header chunk.
    // It is immediately followed by the offset table, so we cast a pointer to that.
	DMUS_OFFSETTABLE* pOffsetTable = (DMUS_OFFSETTABLE *)(((BYTE*)pvData) + sizeof(DMUS_DOWNLOADINFO));
    // We need a byte pointer to the data to use in the process of resolving all the offset table pointers.
    char *pcData = (char *) pvData;

	V_BUFPTR_READ(pvData,pInfo->cbSize);

    // Create a copy of the offset table. 
    ppvOffsetTable = new void *[pInfo->dwNumOffsetTableEntries];
    if (ppvOffsetTable)	
    {
        // Each index in the offset table is an offset from the beginning of the download chunk to 
        // a position in the memory where a specific structure resides. 
        // Scan through the table and convert these offsets into actual memory 
        // addresses, and store these in the ppvOfsetTable array. These
        // will be used by the parsing code to resolve indexes to actual
        // memory addresses.
	    DWORD dwIndex;
	    for (dwIndex = 0; dwIndex < pInfo->dwNumOffsetTableEntries; dwIndex++)
	    {
            // First, make sure the offset is not out of bounds. 
		    if (pOffsetTable->ulOffsetTable[dwIndex] >= pInfo->cbSize)
		    {
			    return DMUS_E_BADOFFSETTABLE;	// Bad!
		    }
            // Go ahead and calculate the actual memory address and store it. 
            ppvOffsetTable[dwIndex] = (void *) &pcData[pOffsetTable->ulOffsetTable[dwIndex]];
	    }
        // Once the offset table is constructed, we can pass it to the appropriate parsing routine.
        // There are three types of download chunks: DMUS_DOWNLOADINFO_INSTRUMENT,
        // DMUS_DOWNLOADINFO_INSTRUMENT2, and DMUS_DOWNLOADINFO_WAVE. 
        // The two instrument formats exist because DMUS_DOWNLOADINFO_INSTRUMENT was changed to
        // support a variable articulation to support DLS2 in the DX7 timeframe. In truth,
        // only DMUS_DOWNLOADINFO_INSTRUMENT2 and DMUS_DOWNLOADINFO_WAVE need be supported, 
        // but we continue with DMUS_DOWNLOADINFO_INSTRUMENT support in this example.
        // Depending on the type of download, we call the appropriate method, which then
        // parses the data.
        // To let dmusic understand that it does support the DMUS_DOWNLOADINFO_INSTRUMENT2,
        // the synth must respond positively to the KsProperty Query GUID_DMUS_PROP_INSTRUMENT2,
        // request (please see CUserModeSynth::KsProperty() for implementation details.)
	    if (pInfo->dwDLType == DMUS_DOWNLOADINFO_INSTRUMENT) // Instrument.
	    {
            // Instrument does not need to keep the  download chunk allocated, so indicate that
            // the caller can free it.
		    *pbFree = TRUE;
            // Call the download instrument method, indicating that this is a DX6 format download.
		    hr = DownloadInstrument(phDownload, pInfo, ppvOffsetTable, ppvOffsetTable[0],FALSE); 
	    }
	    if (pInfo->dwDLType == DMUS_DOWNLOADINFO_INSTRUMENT2) // New instrument format.
	    {
		    *pbFree = TRUE;
            // Call the download instrument method, indicating that this is a DX7 or up format download.
		    hr = DownloadInstrument(phDownload, pInfo, ppvOffsetTable, ppvOffsetTable[0],TRUE); 
	    }

	    else if (pInfo->dwDLType == DMUS_DOWNLOADINFO_WAVE) // Wave.
	    {
            // Wave does need to keep the  download chunk allocated, because it includes the
            // wave data buffer, which it will play directly out of, so indicate that
            // the caller must not free it until it is unloaded.
		    *pbFree = FALSE;
		    hr = DownloadWave(phDownload, pInfo, ppvOffsetTable, ppvOffsetTable[0]); 
	    }
        delete ppvOffsetTable;
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
	return hr;
}

/* Unload
   The unload method is called by DMusic when it is unloading a previously downloaded instrument
   or wave chunk. This instructs the synthesizer to find the object that was downloaded (identified
   by the handle, hDownload, that was generated by the call to Download) and remove it.
   If the object was using the original download chunk, it needs to notify the caller  (dmusic) 
   when it is done using it so the memory can then be freed. This is not necessarily at the time of the 
   download call because wave data may currently be in use by a performing note. So, a pointer
   to a callback function is also provided and the synthesizer must call this function at the time
   the memory is no longer in use.
*/

HRESULT CInstManager::Unload(HANDLE hDownload, // Handle of previously downloaded wave or instrument.
							 HRESULT ( CALLBACK *lpFreeHandle)(HANDLE,HANDLE), // Callback function for releasing downloaded memory
							 HANDLE hUserData) // Parameter to pass to callback function, to indicate which download is being freed.

{
    DWORD dwIndex;
	EnterCriticalSection(&m_CriticalSection);
    // First, check to see if this is an instrument. We keep all the instruments in a hash table to speed up
    // access.
    for (dwIndex = 0; dwIndex < INSTRUMENT_HASH_SIZE; dwIndex++)
    {
        CInstrument *pInstrument = m_InstrumentList[dwIndex].GetHead();
        for (;pInstrument != NULL; pInstrument = pInstrument->GetNext())
        {
            // If the instrument matches the download handle, remove it from the list
            // and delete it. There is no need to callback for releasing the memory because
            // the synthesizer did not hang on to the original downloaded instrument memory.
            if (pInstrument == (CInstrument *) hDownload) 
            {
                // To help debug applications, print the patch number of the instrument being unloaded.
                Trace(1,"Unloading instrument %lx\n",pInstrument->m_dwProgram);
			    m_InstrumentList[dwIndex].Remove(pInstrument);
			    delete pInstrument;
			    LeaveCriticalSection(&m_CriticalSection);
			    return S_OK;
		    }
	    }
    }
    // If it wasn't an instrument, try the wave list. Again, they are arranged in a hash table to increase
    // access speed.
    for (dwIndex = 0; dwIndex < WAVE_HASH_SIZE; dwIndex++)
    {
        CWave *pWave = m_WavePool[dwIndex].GetHead();
        for (;pWave != NULL;pWave = pWave->GetNext())
        {
            // If the wave matches the download handle, remove it from the list.
            // Also, store the callback function, lpFreeHandle, and associated instance parameter, 
            // hUserData, in the wave. Remove the wave from the wave pool, so it can no longer
            // be connected with another instrument.
            // When the last instance of a voice that is playing the wave finishes, lpFreeHandle
            // will be called and the caller (DirectMusic dll) will be able to free the memory.
            // Usually, the wave is not currently being played and this happens instantly.
		    if (pWave == (CWave *) hDownload)
		    {
                Trace(3,"Unloading wave %ld\n",pWave->m_dwID);
			    m_WavePool[dwIndex].Remove(pWave);
			    pWave->m_hUserData = hUserData;
			    pWave->m_lpFreeHandle = lpFreeHandle;
			    pWave->Release();
			    LeaveCriticalSection(&m_CriticalSection);
			    return S_OK;
		    }
	    }
    }
	LeaveCriticalSection(&m_CriticalSection);
	return E_FAIL;
}




