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

#ifdef DMSYNTH_MINIPORT
#include "common.h"
#include <math.h>
#include "muldiv32.h"
#else
#include "simple.h"
#include <mmsystem.h>
#include <dmusicc.h>
#include <dmusics.h>
#include "synth.h"
#include <math.h>
#include <stdio.h>
#include "csynth.h"
#endif

#ifdef _X86_
#define MMX_ENABLED 1
#endif

#ifdef DBG
extern DWORD sdwDebugLevel;
#endif

CVoiceLFO::CVoiceLFO()

{
    m_pModWheelIn = NULL;
}

short CVoiceLFO::m_snSineTable[256];

void CVoiceLFO::Init()
{
    double flTemp;
    long lIndex;

    for (lIndex = 0;lIndex < 256;lIndex++)
    {
        flTemp = lIndex;
        flTemp *= 6.283185307;
        flTemp /= 256.0;
        flTemp = sin(flTemp);
        flTemp *= 100.0;
        m_snSineTable[lIndex] = (short) flTemp;
    }
}


STIME CVoiceLFO::StartVoice(CSourceLFO *pSource, 
                    STIME stStartTime,CModWheelIn * pModWheelIn)
{
    m_pModWheelIn = pModWheelIn;
    m_Source = *pSource;
    m_stStartTime = stStartTime;
    if ((m_Source.m_prMWPitchScale == 0) && (m_Source.m_vrMWVolumeScale == 0) &&
        (m_Source.m_prPitchScale == 0) && (m_Source.m_vrVolumeScale == 0))
    {
        m_stRepeatTime = 44100;
    }
    else
    {
        m_stRepeatTime = 2097152 / m_Source.m_pfFrequency; // (1/8 * 256 * 4096 * 16)
    }
    return (m_stRepeatTime);
}

long CVoiceLFO::GetLevel(STIME stTime, STIME *pstNextTime)

{
    stTime -= (m_stStartTime + m_Source.m_stDelay);
    if (stTime < 0) 
    {
        *pstNextTime = -stTime;
        return (0);
    }
    *pstNextTime = m_stRepeatTime;
    stTime *= m_Source.m_pfFrequency;
    stTime = stTime >> (12 + 4); // We've added 4 extra bits of resolution...
    return (m_snSineTable[stTime & 0xFF]);
}

VREL CVoiceLFO::GetVolume(STIME stTime, STIME *pstNextTime)

{
    VREL vrVolume = m_pModWheelIn->GetModulation(stTime);
    vrVolume *= m_Source.m_vrMWVolumeScale;
    vrVolume /= 127;
    vrVolume += m_Source.m_vrVolumeScale;
    vrVolume *= GetLevel(stTime,pstNextTime);
    vrVolume /= 100;
    return (vrVolume);
}

PREL CVoiceLFO::GetPitch(STIME stTime, STIME *pstNextTime)

{
    PREL prPitch = m_pModWheelIn->GetModulation(stTime);
    prPitch *= m_Source.m_prMWPitchScale;
    prPitch /= 127;
    prPitch += m_Source.m_prPitchScale;
    prPitch *= GetLevel(stTime,pstNextTime);
    prPitch /= 100;
    return (prPitch);
}

CVoiceEG::CVoiceEG()
{
    m_stStopTime = 0;
}

short CVoiceEG::m_snAttackTable[201];

void CVoiceEG::Init()
{
	double flTemp;
    long lV;

	m_snAttackTable[0] = 0;
    for (lV = 1;lV <= 200; lV++)
    {
        flTemp = lV;
        flTemp /= 200.0;
        flTemp *= flTemp;
        flTemp = log10(flTemp);
        flTemp *= 10000.0;
        flTemp /= 96.0;
        flTemp += 1000.0;
        m_snAttackTable[lV] = (short) flTemp;
    }   
}

void CVoiceEG::StopVoice(STIME stTime)

{
    m_Source.m_stRelease *= GetLevel(stTime,&m_stStopTime,TRUE);    // Adjust for current sustain level.
    m_Source.m_stRelease /= 1000;
    m_stStopTime = stTime;
}

void CVoiceEG::QuickStopVoice(STIME stTime, DWORD dwSampleRate)

{
    m_Source.m_stRelease *= GetLevel(stTime,&m_stStopTime,TRUE);    // Adjust for current sustain level.
    m_Source.m_stRelease /= 1000;
    dwSampleRate /= 70;
    if (m_Source.m_stRelease > (long) dwSampleRate)
    {
        m_Source.m_stRelease = dwSampleRate;
    }
    m_stStopTime = stTime;
}

STIME CVoiceEG::StartVoice(CSourceEG *pSource, STIME stStartTime, 
                    WORD nKey, WORD nVelocity)

{
    m_stStartTime = stStartTime;
    m_stStopTime = 0x7fffffffffffffff;      // set to indefinite future
    m_Source = *pSource;

    // apply velocity to attack length scaling here
    m_Source.m_stAttack *= CDigitalAudio::PRELToPFRACT(nVelocity * m_Source.m_trVelAttackScale / 127);
	m_Source.m_stAttack /= 4096;

    m_Source.m_stDecay *= CDigitalAudio::PRELToPFRACT(nKey * m_Source.m_trKeyDecayScale / 127);
    m_Source.m_stDecay /= 4096;

    m_Source.m_stDecay *= (1000 - m_Source.m_pcSustain);
    m_Source.m_stDecay /= 1000;
    return ((STIME)m_Source.m_stAttack);
}

BOOL CVoiceEG::InAttack(STIME st)
{
    // has note been released?
    if (st >= m_stStopTime)
        return FALSE;

    // past length of attack?
    if (st >= m_stStartTime + m_Source.m_stAttack)
        return FALSE;

    return TRUE;
}
    
BOOL CVoiceEG::InRelease(STIME st)
{
    // has note been released?
    if (st > m_stStopTime)
        return TRUE;

    return FALSE;
}
    
long CVoiceEG::GetLevel(STIME stEnd, STIME *pstNext, BOOL fVolume)

{
    long lLevel = 0;
    if (stEnd <= m_stStopTime)
    {
        stEnd -= m_stStartTime;
        // note not released yet.
        if (stEnd < m_Source.m_stAttack)
        {
            // still in attack
            lLevel = 1000 * (long) stEnd;
            if (m_Source.m_stAttack)
            {
                lLevel /= (long) m_Source.m_stAttack;
            }
            else // This should never happen, but it does...
            {
                lLevel = 0;
            }
            *pstNext = m_Source.m_stAttack - stEnd;
            if (lLevel < 0) lLevel = 0;
            if (lLevel > 1000) lLevel = 1000;
            if (fVolume)
            {
                lLevel = m_snAttackTable[lLevel / 5];
            }
        }
        else 
        {
            stEnd -= m_Source.m_stAttack;
            
            if (stEnd < m_Source.m_stDecay)
            {
                // still in decay
                lLevel = (1000 - m_Source.m_pcSustain) * (long) stEnd;
                lLevel /= (long) m_Source.m_stDecay;
                lLevel = 1000 - lLevel;
// To improve the decay curve, set the next point to be 1/4, 1/2, or end of slope. 
// To avoid close duplicates, fudge an extra 100 samples.
				if (stEnd < ((m_Source.m_stDecay >> 2) - 100))
				{
					*pstNext = (m_Source.m_stDecay >> 2) - stEnd;
				}	
				else if (stEnd < ((m_Source.m_stDecay >> 1) - 100))
				{
					*pstNext = (m_Source.m_stDecay >> 1) - stEnd;
				}
				else
				{
					*pstNext = m_Source.m_stDecay - stEnd;  // Next is end of decay.
				}
			}
            else
            {
                // in sustain
                lLevel = m_Source.m_pcSustain;
                *pstNext = 44100;
            }
        }
    }
    else
    {
        STIME stBogus;
        // in release
        stEnd -= m_stStopTime;

        if (stEnd < m_Source.m_stRelease)
        {
            lLevel = GetLevel(m_stStopTime,&stBogus,fVolume) * (long) (m_Source.m_stRelease - stEnd);
            lLevel /= (long) m_Source.m_stRelease;
			if (stEnd < ((m_Source.m_stRelease >> 2) - 100))
			{
				*pstNext = (m_Source.m_stRelease >> 2) - stEnd;
			}	
			else if (stEnd < ((m_Source.m_stRelease >> 1) - 100))
			{
				*pstNext = (m_Source.m_stRelease >> 1) - stEnd;
			}
			else
			{
				*pstNext = m_Source.m_stRelease - stEnd;  // Next is end of decay.
			}
        }
        else
        {
            lLevel = 0;   // !!! off
            *pstNext = 0x7FFFFFFFFFFFFFFF;
        }
    }

    return lLevel;
}

VREL CVoiceEG::GetVolume(STIME stTime, STIME *pstNextTime)

{
    VREL vrLevel = GetLevel(stTime, pstNextTime, TRUE) * 96;
    vrLevel /= 10;
    vrLevel = vrLevel - 9600;
    return vrLevel;
}

PREL CVoiceEG::GetPitch(STIME stTime, STIME *pstNextTime)

{
    PREL prLevel;
    if (m_Source.m_sScale != 0)
    {
        prLevel = GetLevel(stTime, pstNextTime,FALSE);
        prLevel *= m_Source.m_sScale;
        prLevel /= 1000;
    }
    else
    {
        *pstNextTime = 44100;
        prLevel = 0;
    }
    return prLevel;
}

CDigitalAudio::CDigitalAudio()
{
    m_pfBasePitch = 0;
    m_pfLastPitch = 0;
    m_pfLastSample = 0;
    m_pfLoopEnd = 0;
    m_pfLoopStart = 0;
    m_pfSampleLength = 0;
    m_prLastPitch = 0;
    m_vrLastLVolume = 0;
    m_vrLastRVolume = 0;
    m_vrBaseLVolume = 0;
    m_vrBaseRVolume = 0;
    m_vfLastLVolume = 0;
    m_vfLastRVolume = 0;
	m_ullLastSample = 0;
	m_ullLoopStart = 0;
	m_ullLoopEnd = 0;
	m_ullSampleLength = 0;
	m_fElGrande = FALSE;
};

PFRACT CDigitalAudio::m_spfCents[201];
PFRACT CDigitalAudio::m_spfSemiTones[97];
VFRACT CDigitalAudio::m_svfDbToVolume[(MAXDB - MINDB) * 10 + 1];
BOOL CDigitalAudio::m_sfMMXEnabled = FALSE;

BOOL MultiMediaInstructionsSupported();
#pragma optimize("", off) // Optimize causes crash! Argh!

void CDigitalAudio::Init()
{
	double flTemp;
    VREL    vrdB;

#ifdef MMX_ENABLED
	m_sfMMXEnabled = MultiMediaInstructionsSupported();
#endif // MMX_ENABLED
    for (vrdB = MINDB * 10;vrdB <= MAXDB * 10;vrdB++)
    {
        flTemp = vrdB;
        flTemp /= 100.0;
        flTemp = pow(10.0,flTemp);
        flTemp = pow(flTemp,0.5);   // square root.
        flTemp *= 4095.0; // 2^12th, but avoid overflow...
        m_svfDbToVolume[vrdB - (MINDB * 10)] = (long) flTemp;
    }

    PREL prRatio;

    for (prRatio = -100;prRatio <= 100;prRatio++)
    {
        flTemp = prRatio;
        flTemp /= 1200.0;
        flTemp = pow(2.0,flTemp);
        flTemp *= 4096.0;
        m_spfCents[prRatio + 100] = (long) flTemp;
    }
    
    for (prRatio = -48;prRatio <= 48;prRatio++)
    {
        flTemp = prRatio;
        flTemp /= 12.0;
        flTemp = pow(2.0,flTemp);
        flTemp *= 4096.0;
        m_spfSemiTones[prRatio + 48] = (long) flTemp;
    }
}
#pragma optimize("", on)

VFRACT CDigitalAudio::VRELToVFRACT(VREL vrVolume)
{
    vrVolume /= 10;
    if (vrVolume < MINDB * 10) vrVolume = MINDB * 10;
    else if (vrVolume >= MAXDB * 10) vrVolume = MAXDB * 10;
    return (m_svfDbToVolume[vrVolume - MINDB * 10]);
}

PFRACT CDigitalAudio::PRELToPFRACT(PREL prPitch)

{
    PFRACT pfPitch = 0;
    PREL prOctave;
    if (prPitch > 100)
    {
		if (prPitch > 4800)
		{
			prPitch = 4800;
		}
        prOctave = prPitch / 100;
        prPitch = prPitch % 100;
        pfPitch = m_spfCents[prPitch + 100];
        pfPitch <<= prOctave / 12;
        prOctave = prOctave % 12;
        pfPitch *= m_spfSemiTones[prOctave + 48];
        pfPitch >>= 12;
    }
    else if (prPitch < -100)
    {
		if (prPitch < -4800)
		{
			prPitch = -4800;
		}
        prOctave = prPitch / 100;
        prPitch = (-prPitch) % 100;
        pfPitch = m_spfCents[100 - prPitch];
        pfPitch >>= ((-prOctave) / 12);
        prOctave = (-prOctave) % 12;
        pfPitch *= m_spfSemiTones[48 - prOctave];
        pfPitch >>= 12;
    }
    else
    {
        pfPitch = m_spfCents[prPitch + 100];
    }
    return (pfPitch);
}

void CDigitalAudio::ClearVoice()

{
    if (m_Source.m_pWave != NULL)
    {
		m_Source.m_pWave->PlayOff();
        m_Source.m_pWave->Release();    // Releases wave structure.
        m_Source.m_pWave = NULL;
    }
}

STIME CDigitalAudio::StartVoice(CSynth *pSynth,
                               CSourceSample *pSample, 
                               VREL vrBaseLVolume,
                               VREL vrBaseRVolume,
                               PREL prBasePitch,
                               long lKey)
{
    m_vrBaseLVolume = vrBaseLVolume;
    m_vrBaseRVolume = vrBaseRVolume;
    m_vfLastLVolume = VRELToVFRACT(MIN_VOLUME); 
    m_vfLastRVolume = VRELToVFRACT(MIN_VOLUME);
    m_vrLastLVolume = 0;
    m_vrLastRVolume = 0;
    m_prLastPitch = 0;
    m_Source = *pSample;
	m_pnWave = pSample->m_pWave->m_pnWave;
    m_pSynth = pSynth;
    pSample->m_pWave->AddRef(); // Keeps track of Wave usage.
    pSample->m_pWave->PlayOn();
	prBasePitch += pSample->m_prFineTune;
    prBasePitch += ((lKey - pSample->m_bMIDIRootKey) * 100);
    m_pfBasePitch = PRELToPFRACT(prBasePitch);
    m_pfBasePitch *= pSample->m_dwSampleRate;
    m_pfBasePitch /= pSynth->m_dwSampleRate;
    m_pfLastPitch = m_pfBasePitch;
	
	m_fElGrande = pSample->m_dwSampleLength >= 0x80000;		// Greater than 512k.
    if ((pSample->m_dwLoopEnd - pSample->m_dwLoopStart) >= 0x80000)
    {   // We can't handle loops greater than 1 meg!
        m_Source.m_bOneShot = TRUE;
    }
    m_ullLastSample = 0;
	m_ullLoopStart = pSample->m_dwLoopStart;
	m_ullLoopStart = m_ullLoopStart << 12;
	m_ullLoopEnd = pSample->m_dwLoopEnd;
	m_ullLoopEnd = m_ullLoopEnd << 12;
	m_ullSampleLength = pSample->m_dwSampleLength;
	m_ullSampleLength = m_ullSampleLength << 12;
    m_pfLastSample = 0;
    m_pfLoopStart = (long) m_ullLoopStart;
    m_pfLoopEnd = (long) m_ullLoopEnd;
    if (m_pfLoopEnd <= m_pfLoopStart) // Should never happen, but death if it does!
    {
        m_Source.m_bOneShot = TRUE;
    }
	if (m_fElGrande)
	{
		m_pfSampleLength = 0x7FFFFFFF;
	}
	else
	{
		m_pfSampleLength = (long) m_ullSampleLength;
    }
	return (0); // !!! what is this return value?
}

/*	If the wave is bigger than one meg, the index can overflow. 
	Solve this by assuming no mix session will ever be as great
	as one meg AND loops are never that long. We keep all our
	fractional indexes in two variables. In one case, m_pfLastSample,
	is the normal mode where the lower 12 bits are the fraction and 
	the upper 20 bits are the index. And, m_ullLastSample
	is a LONGLONG with an extra 32 bits of index. The mix engine
	does not want the LONGLONGs, so we need to track the variables
	in the LONGLONGs and prepare them for the mixer as follows:
	Prior to mixing,
	if the sample is large (m_fElGrande is set), BeforeSampleMix()
	is called. This finds the starting point for the mix, which 
	is either the current position or the start of the loop, 
	whichever is earlier. It subtracts this starting point from
	the LONGLONG variables and stores an offset in m_dwAddressUpper.
	It also adjusts the pointer to the wave data appropriately.
	AfterSampleMix() does the inverse, reconstructing the the LONGLONG
	indeces and returning everthing back to normal.
*/

void CDigitalAudio::BeforeBigSampleMix()

{
	if (m_fElGrande)
	{
		ULONGLONG ullBase = 0;
		DWORD dwBase;
		if (m_Source.m_bOneShot)
		{
			ullBase = m_ullLastSample;
		}
		else
		{
			if (m_ullLastSample < m_ullLoopStart)
			{
				ullBase = m_ullLastSample;
			}
			else
            { 
				ullBase = m_ullLoopStart;
			}
		}
		ullBase >>= 12;
		dwBase = (DWORD) ullBase & 0xFFFFFFFE;		// Clear bottom bit so 8 bit pointer aligns with short.
		ullBase = dwBase;
		ullBase <<= 12;
		m_dwAddressUpper = dwBase;
		m_pfLastSample = (long) (m_ullLastSample - ullBase);
        if ((m_ullLoopEnd - ullBase) < 0x7FFFFFFF)
        {
		    m_pfLoopStart = (long) (m_ullLoopStart - ullBase);
		    m_pfLoopEnd = (long) (m_ullLoopEnd - ullBase);
        }
        else
        {
            m_pfLoopStart = 0;
            m_pfLoopEnd = 0x7FFFFFFF;
        }
		ullBase = m_ullSampleLength - ullBase;
		if (ullBase > 0x7FFFFFFF)
		{
			m_pfSampleLength = 0x7FFFFFFF;
		}
		else
		{
			m_pfSampleLength = (long) ullBase;
		}
		if (m_Source.m_bSampleType & SFORMAT_8)
		{
			dwBase >>= 1;
		}
		m_pnWave = &m_Source.m_pWave->m_pnWave[dwBase];
	}
}

void CDigitalAudio::AfterBigSampleMix()

{
	m_pnWave = m_Source.m_pWave->m_pnWave;
	if (m_fElGrande)
	{
		ULONGLONG ullBase = m_dwAddressUpper;
		m_ullLastSample = m_pfLastSample;
		m_ullLastSample += (ullBase << 12);
		m_dwAddressUpper = 0;
	}
}

BOOL CDigitalAudio::Mix(short *pBuffer,
                       DWORD dwLength, // length in SAMPLES
                       VREL vrVolumeL,
                       VREL vrVolumeR,
                       PREL prPitch,
                       DWORD dwStereo)

{
    PFRACT pfDeltaPitch;
    PFRACT pfEnd;
    PFRACT pfLoopLen;
    PFRACT pfNewPitch;
    VFRACT vfNewLVolume;
    VFRACT vfNewRVolume;
    VFRACT vfDeltaLVolume;
    VFRACT vfDeltaRVolume;
    DWORD dwPeriod = 64;
    DWORD dwSoFar;
    DWORD dwStart; // position in WORDs
    DWORD dwMixChoice = dwStereo ? SPLAY_STEREO : 0;
    if (dwLength == 0)      // Attack was instant. 
    {
        m_pfLastPitch = (m_pfBasePitch * PRELToPFRACT(prPitch)) >> 12;
        m_vfLastLVolume = VRELToVFRACT(m_vrBaseLVolume + vrVolumeL);
        m_vfLastRVolume = VRELToVFRACT(m_vrBaseRVolume + vrVolumeR);
        m_prLastPitch = prPitch;
        m_vrLastLVolume = vrVolumeL;
        m_vrLastRVolume = vrVolumeR;
        return (TRUE);
    }
	if ((m_Source.m_pWave == NULL) || (m_Source.m_pWave->m_pnWave == NULL))
	{
		return FALSE;
	}
    DWORD dwMax = abs(vrVolumeL - m_vrLastLVolume);
    m_vrLastLVolume = vrVolumeL;
    dwMax = max((long)dwMax,abs(vrVolumeR - m_vrLastRVolume));
    m_vrLastRVolume = vrVolumeR;
    dwMax = max((long)dwMax,abs(prPitch - m_prLastPitch) << 1);
    dwMax >>= 1;
    m_prLastPitch = prPitch;
    if (dwMax > 0)
    {
        dwPeriod = (dwLength << 3) / dwMax;
        if (dwPeriod > 512)
        {
            dwPeriod = 512;
        }
        else if (dwPeriod < 1)
        {
            dwPeriod = 1;
        }
    }
    else
    {
        dwPeriod = 512;     // Make it happen anyway.
    }

	// This makes MMX sound a little better (MMX bug will be fixed)
    dwPeriod += 3;
    dwPeriod &= 0xFFFFFFFC;

	pfNewPitch = m_pfBasePitch * PRELToPFRACT(prPitch);
    pfNewPitch >>= 12;

    pfDeltaPitch = MulDiv(pfNewPitch - m_pfLastPitch,dwPeriod << 8,dwLength);
    vfNewLVolume = VRELToVFRACT(m_vrBaseLVolume + vrVolumeL);
    vfNewRVolume = VRELToVFRACT(m_vrBaseRVolume + vrVolumeR);
    vfDeltaLVolume = MulDiv(vfNewLVolume - m_vfLastLVolume,dwPeriod << 8,dwLength);
    vfDeltaRVolume = MulDiv(vfNewRVolume - m_vfLastRVolume,dwPeriod << 8,dwLength);

    if (m_sfMMXEnabled && (dwLength > 8))
    {
       dwMixChoice |= SPLAY_MMX; 
    }
    dwMixChoice |= m_Source.m_bSampleType;
    dwStart = 0;

    for (;;)
    {
		if (dwLength <= 8)
		{
			dwMixChoice &= ~SPLAY_MMX;
		}
		if (m_fElGrande)
		{
			BeforeBigSampleMix();
		}
        if (m_Source.m_bOneShot)
        {
            pfEnd = m_pfSampleLength;
            pfLoopLen = 0;
        }
        else
        {
            pfEnd = m_pfLoopEnd;
            pfLoopLen = m_pfLoopEnd - m_pfLoopStart;
            if (pfLoopLen <= pfNewPitch)
            {
                return FALSE;
            }
        }
        switch (dwMixChoice)
        {
        case SFORMAT_8 | SPLAY_STEREO : 
            dwSoFar = Mix8(&pBuffer[dwStart],dwLength,dwPeriod,
                vfDeltaLVolume, vfDeltaRVolume,
                pfDeltaPitch, 
                pfEnd, pfLoopLen);
            break;
        case SFORMAT_8 : 
            dwSoFar = MixMono8(&pBuffer[dwStart],dwLength,dwPeriod,
                vfDeltaLVolume,
                pfDeltaPitch, 
                pfEnd, pfLoopLen);
            break;
        case SFORMAT_16 | SPLAY_STEREO : 
            dwSoFar = Mix16(&pBuffer[dwStart],dwLength,dwPeriod,
                vfDeltaLVolume, vfDeltaRVolume,
                pfDeltaPitch, 
                pfEnd, pfLoopLen);
            break;
        case SFORMAT_16 :
            dwSoFar = MixMono16(&pBuffer[dwStart],dwLength,dwPeriod,
                vfDeltaLVolume,
                pfDeltaPitch, 
                pfEnd, pfLoopLen);
            break;
#ifdef MMX_ENABLED
		case SFORMAT_8 | SPLAY_MMX | SPLAY_STEREO : 
            dwSoFar = Mix8X(&pBuffer[dwStart],dwLength,dwPeriod,
                vfDeltaLVolume, vfDeltaRVolume ,
                pfDeltaPitch, 
                pfEnd, pfLoopLen);
		
            break;
		case SFORMAT_16 | SPLAY_MMX | SPLAY_STEREO : 
            dwSoFar = Mix16X(&pBuffer[dwStart],dwLength,dwPeriod,
                vfDeltaLVolume, vfDeltaRVolume,
                pfDeltaPitch, 
                pfEnd, pfLoopLen);
			break; 
		case SFORMAT_8 | SPLAY_MMX : 
            dwSoFar = MixMono8X(&pBuffer[dwStart],dwLength,dwPeriod,
                vfDeltaLVolume,
                pfDeltaPitch, 
                pfEnd, pfLoopLen);
		
            break;
		case SFORMAT_16 | SPLAY_MMX : 
            dwSoFar = MixMono16X(&pBuffer[dwStart],dwLength,dwPeriod,
                vfDeltaLVolume, 
                pfDeltaPitch, 
                pfEnd, pfLoopLen);
			break; 
#endif
        default :
            return (FALSE);
        }
		if (m_fElGrande)
		{
			AfterBigSampleMix();
		}
        if (m_Source.m_bOneShot)
        {
            if (dwSoFar < dwLength) 
            {
                return (FALSE);
            }
            break;
        }
        else
        {
            if (dwSoFar >= dwLength) break;

        // !!! even though we often handle loops in the mix function, sometimes
        // we don't, so we still need this code.
            // otherwise we must have reached the loop's end.
            dwStart += dwSoFar << dwStereo;
            dwLength -= dwSoFar;
            m_pfLastSample -= (m_pfLoopEnd - m_pfLoopStart);  
        }
    }

    m_vfLastLVolume = vfNewLVolume;
    m_vfLastRVolume = vfNewRVolume;
    m_pfLastPitch = pfNewPitch;
    return (TRUE);
}

CVoice::CVoice()
{
	m_pControl = NULL;
    m_pPitchBendIn = NULL;
    m_pExpressionIn = NULL;
	m_dwPriority = 0;
    m_nPart = 0;
    m_nKey = 0;
    m_fInUse = FALSE;
    m_fSustainOn = FALSE;
    m_fNoteOn = FALSE;
	m_fTag = FALSE;
    m_stStartTime = 0;
    m_stStopTime = 0x7fffffffffffffff;
    m_vrVolume = 0;
    m_fAllowOverlap = FALSE;
}

VREL CVoice::m_svrPanToVREL[128];

void CVoice::Init()
{
    static BOOL fBeenHereBefore = FALSE;
    if (fBeenHereBefore) return;
    fBeenHereBefore = TRUE;
    CVoiceLFO::Init();
    CVoiceEG::Init();
    CDigitalAudio::Init();

    WORD nI;
    for (nI = 1; nI < 128; nI++)
    {
        double flTemp;
        flTemp = nI;
        flTemp /= 127.0;
        flTemp = log10(flTemp);
        flTemp *= 1000.0;
        m_svrPanToVREL[nI] = (long) flTemp;
    }  
    m_svrPanToVREL[0] = -2500;
}


void CVoice::StopVoice(STIME stTime)

{
    if (m_fNoteOn)
    {
        if (stTime <= m_stStartTime) stTime = m_stStartTime + 1;
        m_PitchEG.StopVoice(stTime);
        m_VolumeEG.StopVoice(stTime);
        m_fNoteOn = FALSE;
        m_fSustainOn = FALSE;
        m_stStopTime = stTime;
    }
}

void CVoice::QuickStopVoice(STIME stTime)

{
	m_fTag = TRUE;
	if (m_fNoteOn || m_fSustainOn)
	{
		if (stTime <= m_stStartTime) stTime = m_stStartTime + 1;
		m_PitchEG.StopVoice(stTime);
		m_VolumeEG.QuickStopVoice(stTime,m_pSynth->m_dwSampleRate);
		m_fNoteOn = FALSE;
		m_fSustainOn = FALSE;
		m_stStopTime = stTime;
	}
	else
	{
		m_VolumeEG.QuickStopVoice(m_stStopTime,m_pSynth->m_dwSampleRate);
	}
}

BOOL CVoice::StartVoice(CSynth *pSynth,
                        CSourceRegion *pRegion, STIME stStartTime,
                        CModWheelIn * pModWheelIn,
                        CPitchBendIn * pPitchBendIn,
                        CExpressionIn * pExpressionIn,
                        CVolumeIn * pVolumeIn,
                        CPanIn * pPanIn,
                        WORD nKey,WORD nVelocity,
						VREL vrVolume,
						PREL prPitch)
{
    CSourceArticulation * pArticulation = pRegion->m_pArticulation;
	if (pArticulation == NULL)
	{
		return FALSE;
	}
    // if we're going to handle volume later, don't read it now.
    if (!pSynth->m_fAllowVolumeChangeWhilePlayingNote)
        vrVolume += pVolumeIn->GetVolume(stStartTime);
    prPitch += pRegion->m_prTuning;
    m_dwGroup = pRegion->m_bGroup;
    m_fAllowOverlap = pRegion->m_bAllowOverlap;

    m_pSynth = pSynth;

    vrVolume += CMIDIRecorder::VelocityToVolume(nVelocity);
//                 * (long) pArticulation->m_sVelToVolScale) / -9600);

    vrVolume += pRegion->m_vrAttenuation;

    m_lDefaultPan = pRegion->m_pArticulation->m_sDefaultPan;
    // ignore pan here if allowing pan to vary after note starts

    VREL vrLVolume;
    VREL vrRVolume;
    if (pSynth->m_dwStereo && !pSynth->m_fAllowPanWhilePlayingNote) {
        long lPan = pPanIn->GetPan(stStartTime) + m_lDefaultPan;
        if (lPan < 0) lPan = 0;
        if (lPan > 127) lPan = 127;
        vrLVolume = m_svrPanToVREL[127 - lPan] + vrVolume;
        vrRVolume = m_svrPanToVREL[lPan] + vrVolume;
    } else {
        vrLVolume = vrVolume;
        vrRVolume = vrVolume;
    }
    
    m_stMixTime = m_LFO.StartVoice(&pArticulation->m_LFO,
        stStartTime, pModWheelIn);
    STIME stMixTime = m_PitchEG.StartVoice(&pArticulation->m_PitchEG,
        stStartTime, nKey, nVelocity);
    if (stMixTime < m_stMixTime)
    {
        m_stMixTime = stMixTime;
    }
    stMixTime = m_VolumeEG.StartVoice(&pArticulation->m_VolumeEG,
            stStartTime, nKey, nVelocity);
    if (stMixTime < m_stMixTime)
    {
        m_stMixTime = stMixTime;
    }
    if (m_stMixTime > pSynth->m_stMaxSpan)
    {
        m_stMixTime = pSynth->m_stMaxSpan;
    }
    // Make sure we have a pointer to the wave ready:
    if ((pRegion->m_Sample.m_pWave == NULL) || (pRegion->m_Sample.m_pWave->m_pnWave == NULL))
    {
        return (FALSE);     // Do nothing if no sample.
    }
    m_DigitalAudio.StartVoice(pSynth,&pRegion->m_Sample,
        vrLVolume, vrRVolume, prPitch, (long)nKey);

    m_pPitchBendIn = pPitchBendIn;
    m_pExpressionIn = pExpressionIn;
    m_pPanIn = pPanIn;
    m_pVolumeIn = pVolumeIn;
    m_fNoteOn = TRUE;
	m_fTag = FALSE;
    m_stStartTime = stStartTime;
    m_stLastMix = stStartTime - 1;
    m_stStopTime = 0x7fffffffffffffff;
	
    if (m_stMixTime == 0)
    {
        // zero length attack, be sure it isn't missed....

        PREL prPitch = GetNewPitch(stStartTime);
        VREL vrVolume, vrVolumeR;
        GetNewVolume(stStartTime, vrVolume, vrVolumeR);

		if (m_stMixTime > pSynth->m_stMaxSpan)
		{
			m_stMixTime = pSynth->m_stMaxSpan;
		}

		m_DigitalAudio.Mix(NULL, 0,
							   vrVolume, vrVolumeR, prPitch,
							   m_pSynth->m_dwStereo);
    }
    m_vrVolume = 0;
    return (TRUE);
}
    
void CVoice::ClearVoice()

{
    m_fInUse = FALSE;
    m_DigitalAudio.ClearVoice();
}

// return the volume delta at time <stTime>.
// volume is sum of volume envelope, LFO, expression, optionally the
// channel volume if we're allowing it to change, and optionally the current
// pan if we're allowing that to change.
// This will be added to the base volume calculated in CVoice::StartVoice().
void CVoice::GetNewVolume(STIME stTime, VREL& vrVolume, VREL &vrVolumeR)
{
    STIME stMixTime;
    vrVolume = m_VolumeEG.GetVolume(stTime,&stMixTime);
    if (stMixTime < m_stMixTime) m_stMixTime = stMixTime;
    // save pre-LFO volume for code that detects whether this note is off
    m_vrVolume = vrVolume;

    vrVolume += m_LFO.GetVolume(stTime,&stMixTime);
    if (stMixTime < m_stMixTime) m_stMixTime = stMixTime;
    vrVolume += m_pExpressionIn->GetVolume(stTime);

    if (m_pSynth->m_fAllowVolumeChangeWhilePlayingNote)
        vrVolume += m_pVolumeIn->GetVolume(stTime);
    vrVolume += m_pSynth->m_vrGainAdjust;
    vrVolumeR = vrVolume;
   
    // handle pan here if allowing pan to vary after note starts
    if (m_pSynth->m_dwStereo &&
        m_pSynth->m_fAllowPanWhilePlayingNote)
    {
        // add current pan & instrument default pan
        LONG lPan = m_pPanIn->GetPan(stTime) + m_lDefaultPan;

        // don't go off either end....
        if (lPan < 0) lPan = 0;
        if (lPan > 127) lPan = 127;
        vrVolume += m_svrPanToVREL[127 - lPan];
        vrVolumeR += m_svrPanToVREL[lPan];
    }
}

// Returns the current pitch for time <stTime>.
// Pitch is the sum of the pitch LFO, the pitch envelope, and the current
// pitch bend.
PREL CVoice::GetNewPitch(STIME stTime)
{
    STIME stMixTime;
    PREL prPitch = m_LFO.GetPitch(stTime,&stMixTime);
    if (m_stMixTime > stMixTime) m_stMixTime = stMixTime;
    prPitch += m_PitchEG.GetPitch(stTime,&stMixTime);
    if (m_stMixTime > stMixTime) m_stMixTime = stMixTime;
    prPitch += m_pPitchBendIn->GetPitch(stTime); 

    return prPitch;
}


DWORD CVoice::Mix(short *pBuffer,DWORD dwLength,
                 STIME stStart,STIME stEnd)

{
    BOOL fInUse = TRUE;
    BOOL fFullMix = TRUE;
    STIME stEndMix = stStart;

    STIME stStartMix = m_stStartTime;
    if (stStartMix < stStart) 
    {
        stStartMix = stStart;
    }
    if (m_stLastMix >= stEnd)
    {
        return (0);
    }
    if (m_stLastMix >= stStartMix)
    {
        stStartMix = m_stLastMix;
    }
    while (stStartMix < stEnd && fInUse)
    {   
        stEndMix = stStartMix + m_stMixTime;
        
        if (stEndMix > stEnd)
        {
            stEndMix = stEnd;
        }
        m_stMixTime = m_pSynth->m_stMaxSpan;
        if ((m_stLastMix < m_stStopTime) && (m_stStopTime < stEnd))
        {
            if (m_stMixTime > (m_stStopTime - m_stLastMix))
            {
                m_stMixTime = m_stStopTime - m_stLastMix;
            }
        }
        
        PREL prPitch = GetNewPitch(stEndMix);

        VREL vrVolume, vrVolumeR;
        GetNewVolume(stEndMix, vrVolume, vrVolumeR);
        
        if (m_VolumeEG.InRelease(stEndMix)) 
        {
            if (m_vrVolume < PERCEIVED_MIN_VOLUME) // End of release slope
            {
                fInUse = FALSE;
            }
        }

        fFullMix = m_DigitalAudio.Mix(&pBuffer[(stStartMix - stStart) <<
                                                      m_pSynth->m_dwStereo],
                                      (DWORD) (stEndMix - stStartMix),
                                      vrVolume, vrVolumeR, prPitch,
                                      m_pSynth->m_dwStereo);
        stStartMix = stEndMix;
    }
    m_fInUse = fInUse && fFullMix;
    if (!m_fInUse) 
    {
        ClearVoice();
        m_stStopTime = stEndMix;    // For measurement purposes.
    }
    m_stLastMix = stEndMix;
    return (dwLength);
}

