///////////////////////////////////////////////////////////////////////////////
// Copyright (c) Winnov L.P., 1996.  All rights reserved.
// va.cpp: Virtual Audio Class implementation
///////////////////////////////////////////////////////////////////////////////

#include <windows.h>
#include <mmsystem.h>
#include "debug.h"
#include "constant.h"
#include "va.h"
#include "wnvhw.h"
#include "wnvasrc.h"
#include "log.h"
#include "nlstring.h"
#include "registry.h"

///////////////////////////////////////////////////////////////////////////////


#define GRANULARITY		16L
#define WRAP(offset) 		(offset & (WAVI_AUDIOBUFFERSIZE - GRANULARITY))
#define GRANULATE(offset) 	(offset & ~(GRANULARITY-1))

#define DEF_WRITEOFFSET 	0
#define DEF_SYNCH_WINDOW	16			/* synchronization window in samples */

// Private Methods

BYTE gbVerifyBuf [8192];
DWORD gdwVerifyIndex = 0;

BOOL Local_Verify (LPDWORD lp1, LPDWORD lp2, DWORD dwSize)
{
    DWORD i;

    for (i = 0; (i < dwSize); i+=4)
    {
	if (*lp1 != *lp2)
	{
	    TraceString ("\n********** Local_Verify: miscompare at offset=");
	    TraceLong (i);
	    TraceString (", First Read=");
	    TraceLong (*lp1);
	    TraceString (", Second Read=");
	    TraceLong (*lp2);
	    return TRUE;
	}
	lp1++;
	lp2++;
    }
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
// Methods requiring external interaction
///////////////////////////////////////////////////////////////////////////////

DWORD CVa::GetTime (void)
{
    DWORD dwTime;
    DWORD dwDelta;

    dwTime = m_pTime->GetTime ();
    if (m_dwPrevTime)
    {
		dwDelta = dwTime - m_dwPrevTime;
		if (dwDelta > m_dwMaxDeltaTime)
		{
			m_dwMaxDeltaTime = dwDelta;
			TraceString ("\nCVa::GetTime: New max delta=");
			TraceLong ((long)m_dwMaxDeltaTime);
		}
		if (dwDelta < m_dwMinDeltaTime)
		{
			m_dwMinDeltaTime = dwDelta;
			TraceString ("\nCVA::GetTime: New min delta=");
			TraceLong ((long)m_dwMinDeltaTime);
		}

		if (m_dwSamplesPerSecond)
		{	// can check for synch
			if (dwDelta > ((1000 / 4) * WAVI_AUDIOBUFFERSIZE / m_dwSamplesPerSecond))
			{
				m_dwError [VAERR_TIME]++;
				TraceString ("\nCVa::GetTime: exceeded buffer size, resynch. dwDelta=");
				TraceLong (dwDelta);
				m_fSynchronized = FALSE;   // force a re-synch
			}
		}
	}
    m_dwPrevTime = dwTime;
    return dwTime;
}

///////////////////////////////////////////////////////////////////////////////
// hardware interaction methods
///////////////////////////////////////////////////////////////////////////////

void CVa::StartAudio (void)
{
    // Set ACE
    AsrcSet (m_dwAsrc, WNVASRC_ACE, TRUE);
}

void CVa::WriteAudioPointer (void)
{
    m_pHWQ->OutWord (WNVHW_PORT_AUDPTR, (WORD)(m_dwAudioBufferBase>>4));
}

DWORD CVa::GetAudioBufferPhase (PWORD pFreq)
{
    return m_pHWQ->InWord (WNVHW_PORT_FREQ, pFreq);
}

DWORD CVa::GetAudioInterruptStatus (PWORD pISR)
{
    //!!!hack: read clear the int status reg, so that we can
    // reset sometimes...
    //WNVHW_PORT_ITR would do this !!!WARNING WARNING WARNING: this will cause video interrpts to get lost
    return m_pHWQ->InWord (WNVHW_PORT_ISR, pISR);
}

void CVa::GetAudioPtr (void)
{
    m_pHWQ->InWord (WNVHW_PORT_CTLL, &m_wAudioPtr1);	// AFP 12:5
    m_pHWQ->InWord (WNVHW_PORT_CTLL, &m_wAudioPtr2);	// AFP 12:5
    m_pHWQ->InWord (WNVHW_PORT_MMA, &m_wMMA);
}

DWORD CVa::ReadAudioPtr (LPDWORD lpdwAudioPtr)
{
    int nRetry;
    DWORD dwResult;

    for (nRetry = 0; (nRetry < 10); nRetry++)
    {
	m_dwAudioPtr = 0x12345678;    // init to signature
	// read the audio pointer twice
	m_pHWQ->InDword (WNVHW_PORT_CTLL, &m_dwAudioPtr);	// AFP 12:5
	dwResult = m_pHWQ->Flush ();		// flush the command queue
	if (dwResult) return dwResult;	// flush failed

	if (m_dwAudioPtr == 0x12345678)
	{
	    TraceString ("\nCVa::ReadAudioPtr: read failed");
	    m_dwError [VAERR_READAUDIOPTR]++;
	    return VAERR_READAUDIOPTR;
	}

	m_wAudioPtr1 = ((WORD)m_dwAudioPtr) & 0xff;
	m_wAudioPtr2 = (HIWORD(m_dwAudioPtr)) & 0xff;
	if (m_wAudioPtr1 == m_wAudioPtr2)
	{
	    *lpdwAudioPtr = (m_wAudioPtr1 & 0xff) << 5;
	    return VAERR_OK;
	}
    }

    TraceString ("\nCVa::ReadAudioPtr: failed, 1/2=");
    TraceLong (m_wAudioPtr1);
    TraceString ("/");
    TraceLong (m_wAudioPtr2);

    m_dwError [VAERR_READAUDIOPTR]++;
    return VAERR_READAUDIOPTR; // bad audptr
}

DWORD CVa::ReadMMA (LPDWORD lpdwMMA)
{
    DWORD dwResult;

    dwResult = m_pHWQ->InWord (WNVHW_PORT_MMA, &m_wMMA);
    if (dwResult) return dwResult;
    dwResult = m_pHWQ->Flush ();		// flush the command queue
    if (dwResult) return dwResult;

    *lpdwMMA = (DWORD)m_wMMA;

    return VAERR_OK;
}

void CVa::WriteByte (DWORD dwOffset, LPVOID lpSrc, DWORD dwSize)
{
    if (dwSize == 0)
    {
	m_dwError [VAERR_ZEROWRITE]++;
	TraceString ("\n********** CVa::WriteByte: zero length write.");
	return;
    }
    if (dwSize > WAVI_AUDIOBUFFERSIZE)
    {
	m_dwError [VAERR_BIGWRITE]++;
	TraceString ("\n********** CVa::WriteByte: dwSize=");
	TraceLong (dwSize);
	return;
    }

    AsrcWrite (m_dwAsrc, lpSrc, dwSize);    // reformat the data
    m_pHWQ->Write (m_dwAudioBufferBase+dwOffset, lpSrc, dwSize);
}

void CVa::ReadByte (LPVOID lpDst, DWORD dwOffset, DWORD dwSize)
{
    if (dwSize == 0)
    {
	m_dwError [VAERR_ZEROREAD]++;
	TraceString ("\n********** CVa::ReadByte: zero length read.");
	return;
    }
    if (dwSize > WAVI_AUDIOBUFFERSIZE)
    {
	m_dwError [VAERR_BIGREAD]++;
	TraceString ("\n********** CVa::ReadByte: dwSize=");
	TraceLong (dwSize);
	return;
    }
    m_pHWQ->Read (lpDst, m_dwAudioBufferBase+dwOffset, dwSize);

    // reformat the data when it becomes available
    // put the actions to be taken in a queue to process later
    m_Reformat [m_nReformatIndex].lpDst = lpDst;
    m_Reformat [m_nReformatIndex].dwSize = dwSize;
    if (m_nReformatIndex < (NREFORMATS-1))
	m_nReformatIndex++;

    if (m_fVerifyRecord)
    { // also read to the verify buffer
	lpDst = &gbVerifyBuf [gdwVerifyIndex];
	gdwVerifyIndex += dwSize;
 	m_pHWQ->Read (lpDst, m_dwAudioBufferBase+dwOffset, dwSize);
	m_Reformat [m_nReformatIndex].lpDst = lpDst;
	m_Reformat [m_nReformatIndex].dwSize = dwSize;
	if (m_nReformatIndex < (NREFORMATS-1))
	    m_nReformatIndex++;
    }
}

BOOL CVa::IsSamplerStereo (void)
{
    return TRUE;        //!!!not implemented
}

///////////////////////////////////////////////////////////////////////////////
//////////////////////////// Local methods ////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

DWORD CVa::BytesToMilliseconds (DWORD dwSize)
{
    if (m_dwSamplesPerSecond && m_dwBytesPerSample)
    	return ((dwSize * 1000L) / m_dwSamplesPerSecond) / m_dwBytesPerSample;
    else
        return 0;    	
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::cvMillisecondsToBytes (DWORD dwTime)
{
    return GRANULATE(
    	((dwTime * m_dwSamplesPerSecond) / 1000L)
    	 * m_dwBytesPerSample);
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::GetCurrentAudioPosition (void)
{
    return WRAP(cvMillisecondsToBytes (GetTime () - m_dwAudioStartTime));
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::GetCumulativeAudioPosition (void)
{
    return cvMillisecondsToBytes (m_dwEntryTime - m_dwAudioStartTime);
}

///////////////////////////////////////////////////////////////////////////////

void CVa::WriteXfer (LPVOID lpSrc, DWORD dwSize)
{
    DWORD dw;
    
    if (dwSize > WAVI_AUDIOBUFFERSIZE)
    {
	m_dwError [VAERR_BIGXFER]++;
	TraceString ("\n********** CVa::WriteXfer: dwSize too big");
	return;
    }
    dw = WAVI_AUDIOBUFFERSIZE - m_dwWriteOffset;	// bytes left til buffer wrap
    if (dw < dwSize)
    {	// break into two write requests to wrap the data

	// first write starts at current position
	// and goes to the end of the buffer
        WriteByte (m_dwWriteOffset, lpSrc, dw);	

	// second write
	// start at source offset by the size of the first write
	// start at destination offset 0 in the write buffer
	// size is requests size less the size of the first transfer
	m_dwWriteOffset = 0;			    // wrap the offset
	lpSrc = (LPVOID)(((LPBYTE)lpSrc) + dw);	    // offset into source by bytes already transferred
	dw = dwSize - dw;			    // size is bytes not transferred by first write

        WriteByte (m_dwWriteOffset, lpSrc, dw); // from start til whats left
	m_dwWriteOffset += dw;			    // update write offset
    }
    else
    {
    	WriteByte (m_dwWriteOffset, lpSrc, dwSize);   // transfer data
	m_dwWriteOffset += dwSize;			    // update write offset
	if (m_dwWriteOffset >= WAVI_AUDIOBUFFERSIZE)  // wrap
	    m_dwWriteOffset = 0;
    }

#if 0
    TraceString ("\nCVa::WriteXfer: ReadO=");
    TraceLong (m_dwReadOffset);
    TraceString (", WriteO=");
    TraceLong (m_dwWriteOffset);
#endif
}

///////////////////////////////////////////////////////////////////////////////

void CVa::ReadXfer (LPVOID lpDst, DWORD dwSize)
{
    DWORD dw;
    
    if (dwSize > WAVI_AUDIOBUFFERSIZE)
    {
	m_dwError [VAERR_BIGREADXFER]++;
	TraceString ("\n********** CVa::ReadXfer: dwSize too big");
	return;
    }
    dw = WAVI_AUDIOBUFFERSIZE - m_dwReadOffset;
    if (dw < dwSize)
    {	// break in two
	// first read starts at current position
	// and goes til the end of the buffer
        ReadByte (lpDst, m_dwReadOffset, dw);

	// second read
	// source offset is 0, because buffer has wrapped
	// destination offset must be updated by size of the first read
	// size of transfer is requested size less the size of the first transfer
	m_dwReadOffset = 0;			    // wrap the source buffer
	lpDst = (LPVOID)(((LPBYTE)lpDst) + dw);	    // offset by size of first xfer
	dw = dwSize - dw;			    // nock off size of first xfer

        ReadByte (lpDst, m_dwReadOffset, dw);	    // from 0 to what's left
	m_dwReadOffset += dw;			    // update read offset
    }
    else
    {
    	ReadByte (lpDst, m_dwReadOffset, dwSize);
	m_dwReadOffset += dwSize;
	if (m_dwReadOffset >= WAVI_AUDIOBUFFERSIZE)   // wrap
	    m_dwReadOffset = 0;
    }
#if 0
    TraceString ("\nCVa::ReadXfer: ReadO=");
    TraceLong (m_dwReadOffset);
    TraceString (", WriteO=");
    TraceLong (m_dwWriteOffset);
#endif
}

///////////////////////////////////////////////////////////////////////////////

void CVa::ResetAudio (void)
{
    m_dwError [VAERR_RESET]++;
    TraceString ("\nCVa::ResetAudio...");
    // never turn off the audio converter, as it can cause spikes in the output
    // reset the audio pointer in WAVI
    WriteAudioPointer ();
    m_fSynchronized = FALSE;
    m_dwTotalBytesRead = 0;
    m_dwReadOffset = 0;
    m_dwWriteOffset = DEF_WRITEOFFSET;
    m_dwSynchWindow = DEF_SYNCH_WINDOW;
}

///////////////////////////////////////////////////////////////////////////////
// synch the virtualized audio pointer to the location of the
// actual audio pointer

void CVa::SynchRead (LPVOID lpSynchBuf, DWORD dwStart, DWORD dwSize)
{
    LPBYTE lpBuf = (LPBYTE)lpSynchBuf;
    DWORD dw;

    if ((dwStart + dwSize) > WAVI_AUDIOBUFFERSIZE)
    {
	dw = WAVI_AUDIOBUFFERSIZE - dwStart;
	m_pHWQ->Read (lpBuf, m_dwAudioBufferBase + dwStart, dw);
	lpBuf += dw;
	dw = dwSize - dw;
	m_pHWQ->Read (lpBuf, m_dwAudioBufferBase + 0, dw);
    }
    else
    {
	m_pHWQ->Read (lpBuf, m_dwAudioBufferBase + dwStart, dwSize);
    }
}

///////////////////////////////////////////////////////////////////////////////

#define SYNCH_MAXTIME	20

BOOL CVa::FindAudioPtrIndirect (PDWORD pdwAudPtr, PDWORD pdwCurrentTime)
{
    long lSynchStart;
    DWORD dwFlushTime;
    DWORD dwTime;
    DWORD dwLow, dwHigh;
    DWORD i;

    // start at virtual audio ptr less the drift
    lSynchStart = (long)GetCurrentAudioPosition ();
    lSynchStart -= m_dwResynchSize/2;
    lSynchStart = WRAP(lSynchStart);

	m_dwResynchSize &= ~15;
	if (m_dwResynchSize < 16)
		m_dwResynchSize = 16;
	if (m_dwResynchSize > WAVI_AUDIOBUFFERSIZE)
		m_dwResynchSize = WAVI_AUDIOBUFFERSIZE;

    // read a portion of the audio buffer twice.
    SynchRead (m_dwSynchBuf0, (DWORD)lSynchStart, m_dwResynchSize);
    SynchRead (m_dwSynchBuf1, (DWORD)lSynchStart, m_dwResynchSize);

    dwFlushTime = GetTime ();			// time prior to flush
    m_pHWQ->Flush ();				// flush the command queue
    *pdwCurrentTime = GetTime ();			// time after flush
    dwTime = *pdwCurrentTime - dwFlushTime;	// time taken to flush

    //look for the real audio pointer location
    if (dwTime > SYNCH_MAXTIME)
    {
        m_dwError [VAERR_BIGSYNCHTIME]++;
	TraceString ("\nCurrent-Flush=");
	TraceLong (dwTime);
	return FALSE;
    }

    dwLow = dwHigh = (DWORD)-1;

    for (i = 0; i < m_dwResynchSize/sizeof(DWORD); i++)
	if (m_dwSynchBuf0[i] != m_dwSynchBuf1[i])
	{
	    dwLow = i;
	    break;
	}
    for (i = m_dwResynchSize/sizeof(DWORD) -1; i; i--)
	if (m_dwSynchBuf0[i] != m_dwSynchBuf1[i])
	{
	    dwHigh = i;
	    break;
	}

    // audio pointer may not be in detection area
    if ((dwLow == (DWORD)-1) || (dwHigh == (DWORD)-1))
    {
	if (m_dwResynchSize >= WAVI_AUDIOBUFFERSIZE)
	{
	    m_dwError [VAERR_BIGSYNCHSIZE]++;
	    TraceString ("\n***** Resynch failed, no differences found.");
	    return FALSE;
	}

	// make sure the entire buffer gets read within 5 seconds
	m_dwResynchSize += WAVI_AUDIOBUFFERSIZE * POLL_TIME / (5 * 1000);
#if 0
	TraceString ("\nNot detected");
#endif
	return FALSE;
    }

    // CYA
    if (dwLow > dwHigh) 
    {
	m_dwError [VAERR_SYNCHORDER]++;
	TraceString ("\nlow>high");
	return FALSE;
    }
    
    // CYA
    if (BytesToMilliseconds (m_dwBytesPerSample * (dwHigh - dwLow)) > SYNCH_MAXTIME)
    {
	m_dwError [VAERR_SYNCHTIME]++;
	TraceString ("high-low > SYNCH_MAXTIME ms");
	return FALSE;
    }

    // found synch address, back up to get time at start of buffer
    *pdwAudPtr = WRAP(lSynchStart + dwHigh * m_dwBytesPerSample);

    return TRUE;    // found audio ptr
}

///////////////////////////////////////////////////////////////////////////////

BOOL CVa::FindAudioPtr (PDWORD pdwAudPtr, PDWORD pdwCurrentTime)
{
    switch (m_wMMA & WNVHW_FULLID)
    {
case WNVHW_WAVI1:
case WNVHW_WAVI95:
	return FindAudioPtrIndirect (pdwAudPtr, pdwCurrentTime);
case WNVHW_WAVI97:
case WNVHW_WIMP:
        // for WAVI97 and WIMP, read back the audio ptr
	if (m_wAudioPtr1 != m_wAudioPtr2) return FALSE; // bad audptr
        *pdwAudPtr = (m_wAudioPtr1 & 0xff) << 5;
        //*pdwCurrentTime = GetTime ();
	return TRUE;
    }

    return FALSE;   // bad MMA
}

///////////////////////////////////////////////////////////////////////////////

BOOL CVa::Resynch (DWORD dwFlushTime)
{
    DWORD dwAudPtr;
    DWORD dwCurrentTime = dwFlushTime;
    DWORD dwTimeSinceLastResynch;

    if (!FindAudioPtr (&dwAudPtr, &dwCurrentTime))
	return FALSE;	// did not find audio ptr

    dwTimeSinceLastResynch = dwCurrentTime - m_dwAudioStartTime;
#if 0
    m_dwAudioStartTime = dwCurrentTime 
	- BytesToMilliseconds (dwAudPtr)
	+ DRIFT_TIME / 2;
    // reference read position to the new start time
    m_dwTotalBytesRead = m_dwReadOffset;
#else
    DWORD dwUnreadBytes;
    DWORD dwUnreadTime;

    if (dwAudPtr > m_dwReadOffset)
	dwUnreadBytes = dwAudPtr - m_dwReadOffset;
    else
	dwUnreadBytes = WAVI_AUDIOBUFFERSIZE - (m_dwReadOffset - dwAudPtr);
    dwUnreadTime = BytesToMilliseconds (dwUnreadBytes);
    //if (dwUnreadTime > 20)
    //	return FALSE;	// seems unlikely

    m_dwAudioStartTime = dwCurrentTime - dwUnreadTime;
    m_fSynchronized = TRUE;
    m_dwTotalBytesRead = 0;
#endif

#if 1
    TraceString ("\nCVa::Resynch: time=");
    TraceLong (dwCurrentTime);
    TraceString (", DeltaT=");
    TraceLong (dwTimeSinceLastResynch);
    TraceString (", Count=");
    TraceLong ((long)m_dwResynchCount);
    TraceString (", dwAudPtr=");
    TraceLong (dwAudPtr);
    TraceString (", ReadO=");
    TraceLong (m_dwReadOffset);
    TraceString (", WriteO=");
    TraceLong (m_dwWriteOffset);
    TraceString (", dwResynchSize=");
    TraceLong (m_dwResynchSize);
#endif

    m_dwResynchCount++;						// statistics
    m_dwResynchSize = 16;

    // the audio streams will glitch if the new audio ptr
    // is not between the read and write offsets
    if (m_dwReadOffset < m_dwWriteOffset)
	if (m_dwReadOffset < dwAudPtr && dwAudPtr < m_dwWriteOffset)
	    ;
	else 
	{
	    m_dwError [VAERR_GLITCH1]++;
	    TraceString ("\n$$$$$$Glitch 1");
	}
    else
	if (m_dwReadOffset < dwAudPtr || dwAudPtr < m_dwWriteOffset)
	    ;
	else
	{
	    m_dwError [VAERR_GLITCH2]++;
	    TraceString ("\n$$$$$Glitch 2");
	}
    return TRUE;    // did resynch
}

///////////////////////////////////////////////////////////////////////////////

BOOL CVa::SoftResynch (DWORD dwSynchTime)
{
    DWORD dwOldStartTime;

#if 1
    TraceString ("\nCVa::SoftResynch: time=");
    TraceLong (dwSynchTime);
    TraceString (", DeltaT=");
    TraceLong (dwSynchTime - m_dwAudioStartTime);
    TraceString (", Count=");
    TraceLong ((long)m_dwResynchCount);
    TraceString (", ReadO=");
    TraceLong (m_dwReadOffset);
    TraceString (", WriteO=");
    TraceLong (m_dwWriteOffset);
#endif
    
    m_dwTotalBytesRead = m_dwReadOffset - WAVI_AUDIOBUFFERSIZE;	// back up one buffer
    dwOldStartTime = m_dwAudioStartTime;
    m_dwAudioStartTime = dwSynchTime + DRIFT_TIME / 2;		// new starting time
    m_fSynchronized = TRUE;

    m_dwResynchCount++;						// statistics
    m_dwResynchSize = 16;

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////

BOOL CVa::HardResynch (void)
{
    m_dwError [VAERR_HARDRESET]++;
    TraceString ("\nCVa::HardResynch: HARD RESET!");

    m_fSynchronized = FALSE;	// force prime to happen
    m_dwResynchCount++;	// statistics
    m_dwResynchSize = 16;

    return TRUE;
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::WriteNormal (LPVOID lpSrc, DWORD dwSize, LPDWORD lpdwUnwritten)
{
    long lUnwritten;
    
    if (!m_fSynchronized) return VAERR_NOTSYNCHRONIZED;
    lUnwritten = ((long)m_dwReadOffset) - ((long)m_dwWriteOffset);
    lUnwritten = GRANULATE(lUnwritten);	// force granularity
    if (lUnwritten < 0)
    	lUnwritten += (long)WAVI_AUDIOBUFFERSIZE;	// take care of offset wrapping
    *lpdwUnwritten = (DWORD)lUnwritten;
    if (!dwSize) return VAERR_OK;			// just a status request
    if (!lUnwritten)
    {	// read and write offsets are messed up
	// this should never happen, but it does
	// the owner class needs to reset everything
	m_fSynchronized = FALSE;				// force synch loss	
	m_dwError [VAERR_OVERRUN]++;
	return VAERR_OVERRUN;
    }
    if (dwSize & 15) return VAERR_BADBUFFERSIZE;	// enforce granularity
    *lpdwUnwritten = (DWORD)lUnwritten - dwSize;
    WriteXfer (lpSrc, dwSize);
    
    return VAERR_OK;
}    

///////////////////////////////////////////////////////////////////////////////
// Public Methods
///////////////////////////////////////////////////////////////////////////////

CVa::CVa () // contructor
{
    m_fCanReadAudioPtr = FALSE;
    m_fVerifyRecord = FALSE;
    m_fSynchronized = FALSE;
    m_dwSamplesPerSecond = 0;
    m_dwBytesPerSample = 0;
    m_dwAudioStartTime = 0;
    m_dwWriteOffset = 0;
    m_dwReadOffset = 0;
    m_dwTotalBytesRead = 0;
    m_dwSynchWindow = 0;
    m_dwEntryTime = 0;
    m_wEntryBuffer = 0;
    m_wEntryStatus = 0;
    m_dwResynchCount = 0;
    m_dwLastSynchTime = 0;
    m_dwReadDriftSize = 0;
    m_nBoard = 0;
    m_dwPrevTime = 0;
    m_dwMaxDeltaTime = 0;
    m_dwMinDeltaTime = (DWORD)-1;
    m_dwResynchSize = 16;
    m_nReformatIndex = 0;
    m_wMMA = 0;
    m_wAudioPtr1 = 0;
    m_wAudioPtr2 = 0;

    m_pHWQ = NULL;
	m_pHadWrap = NULL;
    m_dwAsrc = 0;

    memset (m_dwError, 0, sizeof (m_dwError)); 
}

CVa::~CVa () {}	// destructor

///////////////////////////////////////////////////////////////////////////////

void CVa::Prolog (void)
{
    m_dwEntryTime = GetTime ();
    GetAudioBufferPhase (&m_wEntryBuffer);
    GetAudioInterruptStatus (&m_wEntryStatus);
}

///////////////////////////////////////////////////////////////////////////////
// return value: TRUE if reset occurred

BOOL CVa::Epilog (void)
{
    DWORD   dwCurrentTime;  // after time
    DWORD   dwFlushTime;    // before time
    DWORD   dwFlags;
    DWORD   dwTime;
    UINT    i;

    m_wEntryStatus = (WORD)-1;
    m_wExitStatus = 0;
    m_wEntryBuffer = 0;
    m_wExitBuffer = (WORD)-1;

    // add exit status to command queue
    GetAudioBufferPhase (&m_wExitBuffer);
    GetAudioInterruptStatus (&m_wExitStatus);
    GetAudioPtr ();

    BOOL fMiscompare = TRUE;
    while (fMiscompare)
    {
	fMiscompare = FALSE;
	dwFlushTime = GetTime ();	// time prior to flush
	m_pHWQ->Flush ();		// flush the command queue
	dwCurrentTime = GetTime ();	// time after flush
	dwTime = dwCurrentTime - m_dwAudioStartTime;	// time since reset

	// process the reformat queue
	if (m_nReformatIndex)
	{
	    if (m_fVerifyRecord)
	    {
		for (i = 0; (i < m_nReformatIndex); i+=2)
		{
		    fMiscompare |= Local_Verify (
			(LPDWORD)m_Reformat [i].lpDst,
			(LPDWORD)m_Reformat [i+1].lpDst,
			m_Reformat [i].dwSize);
		    if (!fMiscompare)
			AsrcRead (m_dwAsrc, m_Reformat [i].lpDst, m_Reformat [i].dwSize);
		}
		if (fMiscompare)
		{
		    m_dwError [VAERR_MISCOMPARE]++;
		    m_pHWQ->Backup ();
		}
		gdwVerifyIndex = 0;
	    }
	    else
	    {
		for (i = 0; (i < m_nReformatIndex); i++)
		{
		    AsrcRead (m_dwAsrc, m_Reformat [i].lpDst, m_Reformat [i].dwSize);
		}
	    }
	}
    }

    m_nReformatIndex = 0;

    if (m_fCanReadAudioPtr) return FALSE;

    // do re-synch
    if (m_wEntryStatus == (UINT)-1)
    {
	m_dwError [VAERR_BADENTRYSTATUS]++;
	TraceString ("\nBad EntryStatus.");
	return FALSE;
    }
    if (m_wExitBuffer == (UINT)-1)
    {
	m_dwError [VAERR_BADEXITBUFFER]++;
	TraceString ("\nBad ExitBuffer.");
	return FALSE;
    }

    if ((long)dwTime < 0) return FALSE;	    // just reset now
    if (!m_fSynchronized) return FALSE;	    // not synchronized
    if (dwTime < 1000) return FALSE;	    // less than one second since last synch

#define ENTRY_STATUS	1
#define EXIT_STATUS	2
#define ENTRY_BUFFER	4
#define EXIT_BUFFER	8
#define OFFSET_ORDER	16
#define TIME_OK		32
#define	ALL_FLAGS (ENTRY_STATUS | EXIT_STATUS | ENTRY_BUFFER | EXIT_BUFFER | OFFSET_ORDER | TIME_OK)

    dwFlags = 0;
    if ((m_wEntryStatus & WNVHW_AISFIELD) == WNVHW_AISFIELD_NOINT)	dwFlags |= ENTRY_STATUS;
    if ((m_wExitStatus & WNVHW_AISFIELD) == WNVHW_AISFIELD_1INT)	dwFlags |= EXIT_STATUS;
    if ((m_wEntryBuffer & WNVHW_APEBIT) == WNVHW_APEBIT)		dwFlags |= ENTRY_BUFFER;
    if ((m_wExitBuffer & WNVHW_APEBIT) == 0)			dwFlags |= EXIT_BUFFER;
    if (m_dwReadOffset > m_dwWriteOffset)				dwFlags |= OFFSET_ORDER;
    if ((dwCurrentTime - dwFlushTime) < 4)			dwFlags |= TIME_OK;

    if (dwFlags == ALL_FLAGS)
	return SoftResynch (dwFlushTime);

    // force synch at least once a minute
    // this will glitch the data streams
    if (dwTime > 60000)
	return HardResynch ();
#if 0
    if (dwTime > 5000)
    {
	TraceString ("\ndwFlags=");
	TraceLong (dwFlags);
    }
#endif
    if (dwTime < 5000) return FALSE;

    // try harder after 5 seconds
    if ((dwCurrentTime - dwFlushTime) < 4)
	return Resynch (dwFlushTime);
    
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////////

__int64 CVa::GetTime64 (void)
{
	return m_pHWQ->GetTime ();
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::Open (UINT nBoardParm, PDWORD pdwHWQStats)
{
    DWORD dwResult;
    DWORD dwMMA;

    if (nBoardParm >= NBOARDS)
    {
		m_dwError [VAERR_BADBOARDNUMBER]++;
		return VAERR_BADBOARDNUMBER;	// bad board number
    }
    m_nBoard = nBoardParm;
    m_dwAsrc = AsrcConstructor (m_nBoard);

	//
	// Create kernel linkage for this board
	//
	m_pHadWrap = new CHadWrap (nBoardParm);
	dwResult = m_pHadWrap->Open ();
	if (dwResult)
	{
		delete m_pHadWrap;
		m_pHadWrap = NULL;
		Close ();
		return dwResult;
	}

	//
	// Get the audio buffer base address
	//
	dwResult = m_pHadWrap->GetAudioBase (&m_dwAudioBufferBase);
	if (dwResult)
	{
		Close ();
		return dwResult;
	}

    m_pTime = new CTime;
    m_pTime->Begin ();

    // start up kernel hardware queue
    m_pHWQ = new CHWQ;
    dwResult = m_pHWQ->Open (m_nBoard, pdwHWQStats);
    if (dwResult)
    {
	m_dwError [VAERR_CHWQOPENFAIL]++;
	LogEvent (dwResult, L"\n********** CVa::Open: CHWQ::Open failed.");
	delete m_pHWQ;
	m_pHWQ = NULL;
	Close ();
	return dwResult;
    }

    dwResult = ReadMMA (&dwMMA);
    if (dwResult) return dwResult;

    m_dwError [VAERR_RESERVED] = dwMMA;
    switch (dwMMA & WNVHW_FULLID)
    {
case WNVHW_WAVI97:
case WNVHW_WIMP:
	m_fCanReadAudioPtr = TRUE;
	break;
default:
	m_fCanReadAudioPtr = FALSE;
	break;
    }

    // determine host interface type
    m_fVerifyRecord = m_pHWQ->IsPCIVideum ();

    // Read registry settings
    CRegistry *pRegistry;
    pRegistry = new CRegistry;
    pRegistry->Open (m_nBoard);
    HKEY hKey;
    hKey = pRegistry->OpenKey (NLS_WNVIRQ);
    m_fVerifyRecord = pRegistry->Read (hKey, NLS_VERIFYRECORD, m_fVerifyRecord);
    pRegistry->Write (hKey, NLS_VERIFYRECORDSTATE, m_fVerifyRecord);
    if (hKey)
    {
	pRegistry->CloseKey (hKey);
	hKey = NULL;
    }
    pRegistry->Close ();
    delete pRegistry;
    pRegistry = NULL;

	//
    // start audio interrupts
	//
	dwResult = m_pHadWrap->ResetInterrupts ();	// clears all the interrupts
	if (dwResult)
	{
		Close ();
		return dwResult;
	}
	dwResult = m_pHadWrap->SetAudioInterruptEnable (TRUE);
	if (dwResult)
	{
		Close ();
		return dwResult;
	}

    m_dwBytesPerSample = IsSamplerStereo ()?4:2;
    m_fSynchronized = FALSE;	// not synchronized
    SetSampleRate (8000);
    Reset ();
    return 0;
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::Close (void)
{
	if (m_pHadWrap)
	{
		m_pHadWrap->SetAudioInterruptEnable (FALSE);
		m_pHadWrap->Close ();
		delete m_pHadWrap;
		m_pHadWrap = NULL;
	}

    if (m_dwAsrc)
    {
	AsrcDestructor (m_dwAsrc);
	m_dwAsrc = 0;
    }

    if (m_pHWQ)
    {
	m_pHWQ->Close ();
	delete m_pHWQ;
	m_pHWQ = NULL;
    }

    if (m_pTime)
    {
	delete m_pTime;
	m_pTime = NULL;
    }

    return VAERR_OK;
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::Begin (void)
{
    return VAERR_OK;
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::End (void)
{
    return VAERR_OK;
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::Reset (void)
{
    TraceString ("\nCVa::Reset...");
    m_dwError [VAERR_EXTRESET]++;
    ResetAudio ();
    return VAERR_OK;
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::SetSampleRate (DWORD dwSampleRate)
{
    m_dwSamplesPerSecond = dwSampleRate;

    // determine the size of the read drift
    if (m_fCanReadAudioPtr)
	m_dwReadDriftSize = 0;
    else
	m_dwReadDriftSize = cvMillisecondsToBytes (READ_DRIFT_TIME);
    return VAERR_OK;
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::GetDrift (PDWORD pdwElapsedTime, 
		     PDWORD pdwSynchTime,
		     PDWORD pdwElapsedBytes)
{
    DWORD dwTime;

    dwTime = GetTime ();
    *pdwSynchTime = dwTime - m_dwAudioStartTime;
    *pdwElapsedTime = dwTime - m_dwPrimeTime;
    *pdwElapsedBytes = m_dwBytesSincePrime;

    return 0;
}

///////////////////////////////////////////////////////////////////////////////
// actually gets unread size

DWORD CVa::GetPos (LPDWORD lpdwUnreadSize)
{
    DWORD dwPos;
    
    dwPos = GetCumulativeAudioPosition ();
    if (m_dwTotalBytesRead > 0xF0000000L)
    {
   	*lpdwUnreadSize = dwPos - m_dwTotalBytesRead; // just had a reset
	if (*lpdwUnreadSize & 0xF0000000L)
	{
	    *lpdwUnreadSize = 0;    // do not allow negative position
	}
    }
    else
    if (dwPos > m_dwTotalBytesRead)
    	*lpdwUnreadSize = dwPos - m_dwTotalBytesRead;
    else
    {
    	*lpdwUnreadSize = 0;
    }
    return dwPos;
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::GetUnreadSize (LPDWORD lpdwUnreadSize)
{
    DWORD dwPos;
    DWORD dwUnreadSize;
            
    if (!m_fSynchronized)
    {
	m_dwError [VAERR_NOTSYNCHRONIZED]++;
	TraceString ("\nCVa::GetUnreadSize: Not Synchronized");
    	ResetAudio ();
    	return VAERR_NOTSYNCHRONIZED;
    }

    if (m_fCanReadAudioPtr)
    {
	DWORD dwResult;
	DWORD dwAudioPtr;

	dwResult = ReadAudioPtr (&dwAudioPtr);
	if (dwResult) return dwResult;

        dwUnreadSize = dwAudioPtr - m_dwReadOffset;
	if (m_dwReadOffset > dwAudioPtr)
	    dwUnreadSize += WAVI_AUDIOBUFFERSIZE;
	m_dwUnreadSize = dwUnreadSize;

	if (m_pdwJitter)
	{
	    DWORD dwDeltaSample;
	    DWORD dwDeltaTime;
	    DWORD dwTime;
	    long lJitter;

	    // calculate the jitter between the sampler and system time.
	    dwDeltaSample = dwAudioPtr - m_dwPrevAudioPtr;
	    if (m_dwPrevAudioPtr > dwAudioPtr)
		dwDeltaSample += WAVI_AUDIOBUFFERSIZE;
	    dwDeltaSample = BytesToMilliseconds (dwDeltaSample);
	    m_dwPrevAudioPtr = dwAudioPtr;

	    dwTime = m_pTime->GetTime ();
	    dwDeltaTime = dwTime - m_dwPrevJitterTime;
	    m_dwPrevJitterTime = dwTime;

    #define MIN_JITTER  -50
    #define MAX_JITTER  49

	    lJitter = dwDeltaTime - dwDeltaSample;
	    if (lJitter < MIN_JITTER)
		lJitter = MIN_JITTER;
	    if (lJitter > MAX_JITTER)
		lJitter = MAX_JITTER;

	    *(m_pdwJitter+lJitter-MIN_JITTER)+=1;
	}
    }
    else
    {
	// this routine is not bracketed by prolog/epilog,
	// so the entry time needs to be set in order
	// to get the correct position
	m_dwEntryTime = GetTime ();
	if (m_dwEntryTime < m_dwAudioStartTime)
	{
	    *lpdwUnreadSize = 0;
	    m_dwError [VAERR_PRIMED]++;
	    return VAERR_OK;   // just did a prime...
	}
	dwPos = GetPos (&dwUnreadSize);
    }

    if (dwUnreadSize > m_dwReadDriftSize)
    {
	*lpdwUnreadSize = dwUnreadSize - m_dwReadDriftSize;
	*lpdwUnreadSize &= ~15;	// mod 16 only
	if (*lpdwUnreadSize <= 0x40)
	{
	    *lpdwUnreadSize = 0;
	    m_dwError [VAERR_SMALL]++;
	}
	else
	if (*lpdwUnreadSize == 0x20)
	    *lpdwUnreadSize = 0x20 - 0x10;
	else
	if (*lpdwUnreadSize == 0x40)
	    *lpdwUnreadSize = 0x40 - 0x10;
	else
	if (*lpdwUnreadSize == 0x80)
	    *lpdwUnreadSize = 0x80 - 0x10;
	else
	if (*lpdwUnreadSize == 0x100)
	    *lpdwUnreadSize = 0x100 - 0x10;
	else
	if (*lpdwUnreadSize == 0x200)
	    *lpdwUnreadSize = 0x200 - 0x10;
	else
	if (*lpdwUnreadSize == 0x400)
	    *lpdwUnreadSize = 0x400 - 0x10;
	else
	if (*lpdwUnreadSize == 0x800)
	    *lpdwUnreadSize = 0x800 - 0x10;
	else
	if (*lpdwUnreadSize == 0x1000)
	    *lpdwUnreadSize = 0x1000 - 0x10;
	else
	if (*lpdwUnreadSize == 0x2000)
	    *lpdwUnreadSize = 0x2000 - 0x10;
    }
    else
    {
	*lpdwUnreadSize = 0;
        m_dwError [VAERR_LESSTHANDRIFT]++;
    }

    return VAERR_OK;
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::Read (LPVOID lpDst, DWORD dwSize, LPDWORD lpdwBytesTransferred, LPDWORD lpdwBytesRemaining)
{
    DWORD dwUnreadSize;
    DWORD dwPos;
        
    if (dwSize > WAVI_AUDIOBUFFERSIZE)
    {
	m_dwError [VAERR_BIGREADSIZE]++;
	TraceString ("\n********** CVa::Read: dwSize to big");
	return 1;
    }

    *lpdwBytesTransferred = 0;	// prepare for failure
    *lpdwBytesRemaining = 0;

    if (!m_fSynchronized)
    {
	m_dwError [VAERR_NOTSYNCHRONIZED]++;
	TraceString ("\nCVa::Read: Not Synchronized");
    	ResetAudio ();
    	return VAERR_NOTSYNCHRONIZED;
    }

    if (m_fCanReadAudioPtr)
    {
	dwUnreadSize = m_dwUnreadSize;
    }
    else
    {
	dwPos = GetPos (&dwUnreadSize);
	dwUnreadSize &= ~15;	// force mod 16
    }

    dwSize &= ~15;		// force mod 16
    if (!dwSize)
    {
	m_dwError [VAERR_BADBUFFERSIZE]++;
	return VAERR_BADBUFFERSIZE;
    }
    if (dwSize > dwUnreadSize)
    {
	m_dwError [VAERR_UNDERRUN]++;
	return VAERR_UNDERRUN;
    }

    *lpdwBytesTransferred = dwSize;
    *lpdwBytesRemaining = dwUnreadSize - dwSize;
    ReadXfer (lpDst, dwSize);
    m_dwTotalBytesRead += dwSize;
    m_dwBytesSincePrime += dwSize;

    return VAERR_OK;
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::Write (LPVOID lpSrc, DWORD dwSize, LPDWORD lpdwUnwritten)
{
    if (dwSize > WAVI_AUDIOBUFFERSIZE)
    {
	m_dwError [VAERR_BIGWRITESIZE]++;
	TraceString ("\n********** CVa::Write: dwSize to big");
	return 1;
    }

    if (!m_fSynchronized)
    {
	m_dwError [VAERR_NOTSYNCHRONIZED]++;
	TraceString ("\nCVa::Write: Not Synchronized.");
	return 1;   // fail
    }

    return WriteNormal (lpSrc, dwSize, lpdwUnwritten);
}

///////////////////////////////////////////////////////////////////////////////
// keep the write offset synchronized to the hardware

DWORD CVa::DummyWrite (DWORD dwSize)
{
    m_dwWriteOffset += dwSize;
    m_dwWriteOffset %= WAVI_AUDIOBUFFERSIZE;  // wrap
#if 0
    TraceString ("\nCVa::DummyWrite: ReadO=");
    TraceLong (m_dwReadOffset);
    TraceString (", WriteO=");
    TraceLong (m_dwWriteOffset);
#endif
    return 0;	    // pass
}

///////////////////////////////////////////////////////////////////////////////
// changing sample rate and/or xtal
// mute the output

extern BYTE bZeroBuf [WAVI_AUDIOBUFFERSIZE];

DWORD CVa::SampleChangeBegin (void)
{
    //!!!hack WriteByte (0, bZeroBuf, WAVI_AUDIOBUFFERSIZE);
    //!!!hack m_pHWQ->Flush ();		// flush the command queue
    return 0;	// pass
}

DWORD CVa::SampleChangeEnd (void)
{
    return 0;	// pass
}

///////////////////////////////////////////////////////////////////////////////
// initial write to audio buffer
// write data to audio buffer, then start up the converter

DWORD CVa::Prime (LPVOID lpSrc, DWORD dwSize)
{
    TraceString ("\nCVa::Prime...");
    //!!!hack ResetAudio ();
    m_dwReadOffset = 0;
    m_dwWriteOffset = 0;
    m_dwTotalBytesRead = 0;
    m_dwBytesSincePrime = 0;
    dwSize &= ~15;	// enforce granularity
    WriteXfer (lpSrc, dwSize);
    m_dwLastSynchTime = m_dwAudioStartTime = GetTime () + DRIFT_TIME/2;
    m_fSynchronized = TRUE;
    StartAudio ();
    m_pHWQ->Flush ();
    m_dwPrimeTime = GetTime ();

    TraceString ("\nCVa::Prime: ReadO=");
    TraceLong (m_dwReadOffset);
    TraceString (", WriteO=");
    TraceLong (m_dwWriteOffset);

    return 0;
}

///////////////////////////////////////////////////////////////////////////////

DWORD CVa::GetStatistics (PDWORD pdwStatistics, DWORD dwMaxSize)
{
    memmove (pdwStatistics, m_dwError, min (dwMaxSize, sizeof (m_dwError)));
    return 0;
}

void CVa::SetJitterPointer (PDWORD pdwJitter)
{
    m_pdwJitter = pdwJitter;
}

///////////////////////////////////////////////////////////////////////////////
//				     End of file
///////////////////////////////////////////////////////////////////////////////
