// ****************************************************************************
//
//  	CDspCommObject.cpp
//
//		Implementation file for EchoGals generic driver DSP interface class.
//
// ----------------------------------------------------------------------------
//
// This file is part of Echo Digital Audio's generic driver library.
// Copyright Echo Digital Audio Corporation (c) 1998 - 2005
// All rights reserved
// www.echoaudio.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// ****************************************************************************
 
#include "CEchoGals.h"
 
#ifdef DSP_56361
#include "LoaderDSP.c"
#endif
 
#define COMM_PAGE_PHYS_BYTES	((sizeof(DspCommPage)+PAGE_SIZE-1)/PAGE_SIZE)*PAGE_SIZE
 
 
/****************************************************************************
 
	Construction and destruction
 
 ****************************************************************************/
 
//===========================================================================
//
// Overload new & delete so memory for this object is allocated
//	from non-paged memory.
//
//===========================================================================
 
PVOID CDspCommObject::operator new( size_t Size )
{
	PVOID 		pMemory;
	ECHOSTATUS 	Status;
	
	Status = OsAllocateNonPaged(Size,&pMemory);
	
	if ( (ECHOSTATUS_OK != Status) || (NULL == pMemory ))
	{
		ECHO_DEBUGPRINTF(("CDspCommObject::operator new - memory allocation failed\n"));
 
		pMemory = NULL;
	}
	else
	{
		memset( pMemory, 0, Size );
	}
 
	return pMemory;
	
}	// PVOID CDspCommObject::operator new( size_t Size )
 
 
VOID  CDspCommObject::operator delete( PVOID pVoid )
{
	if ( ECHOSTATUS_OK != OsFreeNonPaged( pVoid ) )
	{
		ECHO_DEBUGPRINTF( ("CDspCommObject::operator delete memory free "
								 "failed\n") );
	}
}	// VOID  CDspCommObject::operator delete( PVOID pVoid )
 
 
//===========================================================================
//
// Constructor
//
//===========================================================================
 
CDspCommObject::CDspCommObject
(
	PDWORD		pdwDspRegBase,				// Virtual ptr to DSP registers
	PCOsSupport	pOsSupport
)
{
	INT32	i;
 
	ECHO_ASSERT(pOsSupport );
	
	//
	// Init all the basic stuff
	//
	strcpy( m_szCardName, "??????" );
	m_pOsSupport = pOsSupport;				// Ptr to OS Support methods & data
	m_pdwDspRegBase = pdwDspRegBase;		// Virtual addr DSP's register base
	m_bBadBoard = TRUE;						// Set TRUE until DSP loaded
	m_pwDspCode = NULL;						// Current DSP code not loaded
	m_byDigitalMode = DIGITAL_MODE_NONE;
	m_wInputClock = ECHO_CLOCK_INTERNAL;
	m_wOutputClock = ECHO_CLOCK_WORD;
	m_ullLastLoadAttemptTime = (ULONGLONG)(DWORD)(0L - DSP_LOAD_ATTEMPT_PERIOD);	// force first load to go
 
#ifdef MIDI_SUPPORT	
	m_ullNextMidiWriteTime = 0;
#endif
 
	//
	// Create the DSP comm page - this is the area of memory read and written by
	// the DSP via bus mastering
	//
	ECHOSTATUS Status;
	DWORD dwSegmentSize;
	PHYS_ADDR PhysAddr;
	
	Status = pOsSupport->AllocPhysPageBlock(	COMM_PAGE_PHYS_BYTES,
															m_pDspCommPageBlock);
	if (ECHOSTATUS_OK != Status)
	{
		ECHO_DEBUGPRINTF( ("CDspCommObject::CDspCommObject DSP comm page "
								 "memory allocation failed\n") );
		return;
	}
	
	m_pDspCommPage = (PDspCommPage) pOsSupport->
													GetPageBlockVirtAddress( m_pDspCommPageBlock );
	
	pOsSupport->GetPageBlockPhysSegment(m_pDspCommPageBlock,
													0,
													PhysAddr,
													dwSegmentSize);
	m_dwCommPagePhys = PhysAddr;
	
	//
	// Init the comm page
	//
	m_pDspCommPage->dwCommSize = SWAP( sizeof( DspCommPage ) );
													// Size of DSP comm page
	
	m_pDspCommPage->dwHandshake = 0xffffffff;
	m_pDspCommPage->dwMidiOutFreeCount = SWAP( (DWORD) DSP_MIDI_OUT_FIFO_SIZE );
 
	for ( i = 0; i < DSP_MAXAUDIOINPUTS; i++ )
		m_pDspCommPage->InLineLevel[ i ] = 0x00;
													// Set line levels so we don't blast
													// any inputs on startup
	memset( m_pDspCommPage->byMonitors,
			  GENERIC_TO_DSP(ECHOGAIN_MUTED),
			  MONITOR_ARRAY_SIZE );			// Mute all monitors
 
	memset( m_pDspCommPage->byVmixerLevel,
			  GENERIC_TO_DSP(ECHOGAIN_MUTED),
			  VMIXER_ARRAY_SIZE );			// Mute all virtual mixer levels
			  
#ifdef DIGITAL_INPUT_AUTO_MUTE_SUPPORT
 
	m_fDigitalInAutoMute = TRUE;
 
#endif
 
}	// CDspCommObject::CDspCommObject
 
 
//===========================================================================
//
// Destructor
//
//===========================================================================
 
CDspCommObject::~CDspCommObject()
{
	//
	// Go to sleep
	//
	GoComatose();
 
	//
	// Free the comm page
	//
	if ( NULL != m_pDspCommPageBlock )
	{
		m_pOsSupport->FreePhysPageBlock( COMM_PAGE_PHYS_BYTES,
													m_pDspCommPageBlock);
	}
 
	ECHO_DEBUGPRINTF( ( "CDspCommObject::~CDspCommObject() is toast!\n" ) );
 
}	// CDspCommObject::~CDspCommObject()
 
 
 
 
/****************************************************************************
 
	Firmware loading functions
 
 ****************************************************************************/
 
//===========================================================================
//
// ASIC status check - some cards have one or two ASICs that need to be 
// loaded.  Once that load is complete, this function is called to see if
// the load was successful. 
//
// If this load fails, it does not necessarily mean that the hardware is
// defective - the external box may be disconnected or turned off.
//
//===========================================================================
 
BOOL CDspCommObject::CheckAsicStatus()
{
	DWORD	dwAsicStatus;
	DWORD	dwReturn;
 
	//
	// Always succeed if this card doesn't have an ASIC
	//
	if ( !m_bHasASIC )
	{
		m_bASICLoaded = TRUE;
		return TRUE;
	}
			
	// Send the vector command
	m_bASICLoaded = FALSE;
	SendVector( DSP_VC_TEST_ASIC );	
 
	// The DSP will return a value to indicate whether or not the 
	// ASIC is currently loaded
	dwReturn = Read_DSP( &dwAsicStatus );
	if ( ECHOSTATUS_OK != dwReturn )
	{
		ECHO_DEBUGPRINTF(("CDspCommObject::CheckAsicStatus - failed on Read_DSP\n"));
		ECHO_DEBUGBREAK();
		return FALSE;
	}
 
#ifdef ECHO_DEBUG
	if ( (dwAsicStatus != ASIC_LOADED) && (dwAsicStatus != ASIC_NOT_LOADED) )
	{
		ECHO_DEBUGBREAK(); 
	}
#endif
	
	if ( dwAsicStatus == ASIC_LOADED )
		m_bASICLoaded = TRUE;
 
	return m_bASICLoaded;
 
}	// BOOL CDspCommObject::CheckAsicStatus()
 
 
//===========================================================================
//
//	Load ASIC code - done after the DSP is loaded
//
//===========================================================================
 
BOOL CDspCommObject::LoadASIC
(
	DWORD	dwCmd,
	PBYTE	pCode,
	DWORD	dwSize
)
{
	DWORD i;
#ifdef _WIN32
	DWORD dwChecksum = 0;
#endif
 
	ECHO_DEBUGPRINTF(("CDspCommObject::LoadASIC\n"));
 
	if ( !m_bHasASIC )
		return TRUE;
 
#ifdef _DEBUG
	ULONGLONG	ullStartTime, ullCurTime;
	m_pOsSupport->OsGetSystemTime( &ullStartTime );
#endif
 
	// Send the "Here comes the ASIC" command
	if ( ECHOSTATUS_OK != Write_DSP( dwCmd ) )
		return FALSE;
 
	// Write length of ASIC file in bytes
	if ( ECHOSTATUS_OK != Write_DSP( dwSize ) )
		return FALSE;
 
	for ( i = 0; i < dwSize; i++ )
	{
#ifdef _WIN32
		dwChecksum += pCode[i];
#endif	
	
		if ( ECHOSTATUS_OK != Write_DSP( pCode[ i ] ) )
		{
			ECHO_DEBUGPRINTF(("\tfailed on Write_DSP\n"));
			return FALSE;
		}
	}
 
#ifdef _DEBUG
	m_pOsSupport->OsGetSystemTime( &ullCurTime );
	ECHO_DEBUGPRINTF( ("CDspCommObject::LoadASIC took %ld usec.\n",
							(ULONG) ( ullCurTime - ullStartTime ) ) );
	ECHO_DEBUGPRINTF(("ASIC load OK\n"));
#endif
 
#if defined(_WIN32) && (DBG)
	DbgPrint("--- ASIC checksum is 0x%lx\n",dwChecksum);
#endif
 
	return TRUE;
 
}	// BOOL CDspCommObject::LoadASIC( DWORD dwCmd, PBYTE pCode, DWORD dwSize )
 
 
//===========================================================================
//
// InstallResidentLoader
//
// Install the resident loader for 56361 DSPs;  The resident loader
// is on the EPROM on the board for 56301 DSP.
//
// The resident loader is a tiny little program that is used to load
// the real DSP code.
//
//===========================================================================
 
#ifdef DSP_56361
 
ECHOSTATUS CDspCommObject::InstallResidentLoader()
{
	DWORD			dwAddress;
	DWORD			dwIndex;
	INT32			iNum;
	INT32			i;
	DWORD			dwReturn;
	PWORD			pCode;
 
	ECHO_DEBUGPRINTF( ("CDspCommObject::InstallResidentLoader\n") );
	
	//
	// 56361 cards only!
	//
	if (DEVICE_ID_56361 != m_pOsSupport->GetDeviceId() )
		return ECHOSTATUS_OK;
 
	//
	// Look to see if the resident loader is present.  If the resident loader
	// is already installed, host flag 5 will be on.
	//
	DWORD dwStatus;
	dwStatus = GetDspRegister( CHI32_STATUS_REG );
	if ( 0 != (dwStatus & CHI32_STATUS_REG_HF5 ) )
	{
		ECHO_DEBUGPRINTF(("\tResident loader already installed; status is 0x%lx\n",
								dwStatus));
		return ECHOSTATUS_OK;
	}
	//
	// Set DSP format bits for 24 bit mode
	//
	SetDspRegister( CHI32_CONTROL_REG,
						 GetDspRegister( CHI32_CONTROL_REG ) | 0x900 );
 
	//---------------------------------------------------------------------------
	//
	// Loader
	//
	// The DSP code is an array of 16 bit words.  The array is divided up into
	// sections.  The first word of each section is the size in words, followed
	// by the section type.
	//
	// Since DSP addresses and data are 24 bits wide, they each take up two
	// 16 bit words in the array.
	//
	// This is a lot like the other loader loop, but it's not a loop,
	// you don't write the memory type, and you don't write a zero at the end.
	//
	//---------------------------------------------------------------------------
 
	pCode = pwLoaderDSP;
	//
	// Skip the header section; the first word in the array is the size of 
	//	the first section, so the first real section of code is pointed to 
	//	by pCode[0].
	//
	dwIndex = pCode[ 0 ];
	//
	// Skip the section size, LRS block type, and DSP memory type
	//
	dwIndex += 3;
	//	
	// Get the number of DSP words to write
	//
	iNum = pCode[ dwIndex++ ];
	//
	// Get the DSP address for this block; 24 bits, so build from two words
	//
	dwAddress = ( pCode[ dwIndex ] << 16 ) + pCode[ dwIndex + 1 ];
	dwIndex += 2;
	//	
	// Write the count to the DSP
	//
	dwReturn = Write_DSP( (DWORD) iNum );
	if ( dwReturn != 0 )
	{
		ECHO_DEBUGPRINTF( ("CDspCommObject::InstallResidentLoader: Failed to "
								 "write word count!\n") );
		return ECHOSTATUS_DSP_DEAD;
	}
 
	// Write the DSP address
	dwReturn = Write_DSP( dwAddress );
	if ( dwReturn != 0 )
	{
		ECHO_DEBUGPRINTF( ("CDspCommObject::InstallResidentLoader: Failed to "
								 "write DSP address!\n") );
		return ECHOSTATUS_DSP_DEAD;
	}
 
 
	// Write out this block of code to the DSP
	for ( i = 0; i < iNum; i++) // 
	{
		DWORD	dwData;
 
		dwData = ( pCode[ dwIndex ] << 16 ) + pCode[ dwIndex + 1 ];
		dwReturn = Write_DSP( dwData );
		if ( dwReturn != 0 )
		{
			ECHO_DEBUGPRINTF( ("CDspCommObject::InstallResidentLoader: Failed to "
									 "write DSP code\n") );
			return ECHOSTATUS_DSP_DEAD;
		}
 
		dwIndex+=2;
	}
	
	//
	// Wait for flag 5 to come up
	//
	BOOL			fSuccess;
	ULONGLONG 	ullCurTime,ullTimeout;
 
	m_pOsSupport->OsGetSystemTime( &ullCurTime );
	ullTimeout = ullCurTime + 10000L;		// 10m.s.
	fSuccess = FALSE;
	do
	{
		m_pOsSupport->OsSnooze(50);	// Give the DSP some time;
														// no need to hog the CPU
		
		dwStatus = GetDspRegister( CHI32_STATUS_REG );
		if (0 != (dwStatus & CHI32_STATUS_REG_HF5))
		{
			fSuccess = TRUE;
			break;
		}
		
		m_pOsSupport->OsGetSystemTime( &ullCurTime );
 
	} while (ullCurTime < ullTimeout);
	
	if (FALSE == fSuccess)
	{
		ECHO_DEBUGPRINTF(("\tResident loader failed to set HF5\n"));
		return ECHOSTATUS_DSP_DEAD;
	}
		
	ECHO_DEBUGPRINTF(("\tResident loader successfully installed\n"));
 
	return ECHOSTATUS_OK;
	
}	// ECHOSTATUS CDspCommObject::InstallResidentLoader()
 
#endif // DSP_56361
 
 
//===========================================================================
//
// LoadDSP
//
// This loads the DSP code.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::LoadDSP
(
	PWORD	pCode					// Ptr to DSP object code
)
{
	DWORD			dwAddress;
	DWORD			dwIndex;
	INT32			iNum;
	INT32			i;
	DWORD			dwReturn;
	ULONGLONG	ullTimeout, ullCurTime;
	ECHOSTATUS	Status;
 
	ECHO_DEBUGPRINTF(("CDspCommObject::LoadDSP\n"));
	if ( m_pwDspCode == pCode )
	{
		ECHO_DEBUGPRINTF( ("\tDSP is already loaded!\n") );
		return ECHOSTATUS_FIRMWARE_LOADED;
	}
	m_bBadBoard = TRUE;		// Set TRUE until DSP loaded
	m_pwDspCode = NULL;		// Current DSP code not loaded
	m_bASICLoaded = FALSE;	// Loading the DSP code will reset the ASIC
	
	ECHO_DEBUGPRINTF(("CDspCommObject::LoadDSP  Set m_bBadBoard to TRUE\n"));
	
	//
	//	If this board requires a resident loader, install it.
	//
#ifdef DSP_56361
	InstallResidentLoader();
#endif
 
	// Send software reset command	
	if ( ECHOSTATUS_OK != SendVector( DSP_VC_RESET ) )
	{
		m_pOsSupport->EchoErrorMsg(
			"CDspCommObject::LoadDsp SendVector DSP_VC_RESET failed",
			"Critical Failure" );
		return ECHOSTATUS_DSP_DEAD;
	}
 
	// Delay 10us
	m_pOsSupport->OsSnooze( 10L );
 
	// Wait 10ms for HF3 to indicate that software reset is complete	
	m_pOsSupport->OsGetSystemTime( &ullCurTime );
	ullTimeout = ullCurTime + 10000L;		// 10m.s.
 
	// wait for HF3 to be set
wait_for_hf3:
	
	if ( GetDspRegister( CHI32_STATUS_REG ) & CHI32_STATUS_REG_HF3 )
			goto set_dsp_format_bits;
		m_pOsSupport->OsGetSystemTime( &ullCurTime );
		if ( ullCurTime > ullTimeout)
		{
			ECHO_DEBUGPRINTF( ("CDspCommObject::LoadDSP Timeout waiting for "
									 "CHI32_STATUS_REG_HF3\n") );
			m_pOsSupport->EchoErrorMsg(
				"CDspCommObject::LoadDSP SendVector DSP_VC_RESET failed",
				"Critical Failure" );
			return ECHOSTATUS_DSP_TIMEOUT;
		}
	goto wait_for_hf3;
 
 
	// Set DSP format bits for 24 bit mode now that soft reset is done
set_dsp_format_bits:
		SetDspRegister( CHI32_CONTROL_REG,
						 GetDspRegister( CHI32_CONTROL_REG ) | (DWORD) 0x900 );
 
	//---------------------------------------------------------------------------
	// Main loader loop
	//---------------------------------------------------------------------------
 
	dwIndex = pCode[ 0 ];
 
	for (;;)
	{
		INT32	iBlockType;
		INT32	iMemType;
 
		// Total Block Size
		dwIndex++;
		
		// Block Type
		iBlockType = pCode[ dwIndex ];
		if ( iBlockType == 4 )  // We're finished
			break;
 
		dwIndex++;
 
		// Memory Type  P=0,X=1,Y=2
		iMemType = pCode[ dwIndex ]; 
		dwIndex++;
		
		// Block Code Size
		iNum = pCode[ dwIndex ];
		dwIndex++;
		if ( iNum == 0 )			// We're finished
			break;
	
 		// Start Address
		dwAddress = ( (DWORD) pCode[ dwIndex ] << 16 ) + pCode[ dwIndex + 1 ];
//		ECHO_DEBUGPRINTF( ("\tdwAddress %lX\n", dwAddress) );
		dwIndex += 2;
		
		dwReturn = Write_DSP( (DWORD)iNum );
		if ( dwReturn != 0 )
		{
			ECHO_DEBUGPRINTF(("LoadDSP - failed to write number of DSP words\n"));
			return ECHOSTATUS_DSP_DEAD;
		}
 
		dwReturn = Write_DSP( dwAddress );
		if ( dwReturn != 0 )
		{
			ECHO_DEBUGPRINTF(("LoadDSP - failed to write DSP address\n"));
			return ECHOSTATUS_DSP_DEAD;
		}
 
		dwReturn = Write_DSP( (DWORD)iMemType );
		if ( dwReturn != 0 )
		{
			ECHO_DEBUGPRINTF(("LoadDSP - failed to write DSP memory type\n"));
			return ECHOSTATUS_DSP_DEAD;
		}
 
		// Code
		for ( i = 0; i < iNum; i++ )
		{
			DWORD	dwData;
 
			dwData = ( (DWORD) pCode[ dwIndex ] << 16 ) + pCode[ dwIndex + 1 ];
			dwReturn = Write_DSP( dwData );
			if ( dwReturn != 0 )
			{
				ECHO_DEBUGPRINTF(("LoadDSP - failed to write DSP data\n"));
				return ECHOSTATUS_DSP_DEAD;
			}
	
			dwIndex += 2;
		}
//		ECHO_DEBUGPRINTF( ("\tEnd Code Block\n") );
	}
	dwReturn = Write_DSP( 0 );					// We're done!!!
	if ( dwReturn != 0 )
	{
		ECHO_DEBUGPRINTF(("LoadDSP: Failed to write final zero\n"));
		return ECHOSTATUS_DSP_DEAD;
	}
		
 
	// Delay 10us
	m_pOsSupport->OsSnooze( 10L );
 
	m_pOsSupport->OsGetSystemTime( &ullCurTime );
	ullTimeout  = ullCurTime + 500000L;		// 1/2 sec. timeout
 
	while ( ullCurTime <= ullTimeout) 
	{
		//
		// Wait for flag 4 - indicates that the DSP loaded OK
		//
		if ( GetDspRegister( CHI32_STATUS_REG ) & CHI32_STATUS_REG_HF4 )
		{
			SetDspRegister( CHI32_CONTROL_REG,
								 GetDspRegister( CHI32_CONTROL_REG ) & ~0x1b00 );
 
			dwReturn = Write_DSP( DSP_FNC_SET_COMMPAGE_ADDR );
			if ( dwReturn != 0 )
			{
				ECHO_DEBUGPRINTF(("LoadDSP - Failed to write DSP_FNC_SET_COMMPAGE_ADDR\n"));
				return ECHOSTATUS_DSP_DEAD;
			}
				
			dwReturn = Write_DSP( m_dwCommPagePhys );
			if ( dwReturn != 0 )
			{
				ECHO_DEBUGPRINTF(("LoadDSP - Failed to write comm page address\n"));
				return ECHOSTATUS_DSP_DEAD;
			}
 
			//
			// Get the serial number via slave mode.
			// This is triggered by the SET_COMMPAGE_ADDR command.
			//	We don't actually use the serial number but we have to get
			//	it as part of the DSP init vodoo.
			//
			Status = ReadSn();
			if ( ECHOSTATUS_OK != Status )
			{
				ECHO_DEBUGPRINTF(("LoadDSP - Failed to read serial number\n"));
				return Status;
			}
 
			m_pwDspCode = pCode;			// Show which DSP code loaded
			m_bBadBoard = FALSE;			// DSP OK
			
			ECHO_DEBUGPRINTF(("CDspCommObject::LoadDSP  Set m_bBadBoard to FALSE\n"));			
		
			return ECHOSTATUS_OK;
		}
		
		m_pOsSupport->OsGetSystemTime( &ullCurTime );
	}
	
	ECHO_DEBUGPRINTF( ("LoadDSP: DSP load timed out waiting for HF4\n") );	
	
	return ECHOSTATUS_DSP_TIMEOUT;
 
}	// DWORD	CDspCommObject::LoadDSP
 
 
 
 
//===========================================================================
//
// LoadFirmware takes care of loading the DSP and any ASIC code.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::LoadFirmware()
{
	ECHOSTATUS	dwReturn;
	ULONGLONG	ullRightNow;
 
	// Sanity check
	if ( NULL == m_pwDspCodeToLoad || NULL == m_pDspCommPage )
	{
		ECHO_DEBUGBREAK();
		return ECHOSTATUS_NO_MEM;
	}
	
	//
	// Even if the external box is off, an application may still try
	// to repeatedly open the driver, causing multiple load attempts and 
	// making the machine spend lots of time in the kernel.  If the ASIC is not
	// loaded, this code will gate the loading attempts so it doesn't happen
	// more than once per second.
	//
	m_pOsSupport->OsGetSystemTime(&ullRightNow);
	if ( 	(FALSE == m_bASICLoaded) &&
			(DSP_LOAD_ATTEMPT_PERIOD > (ullRightNow - m_ullLastLoadAttemptTime)) )
		return ECHOSTATUS_ASIC_NOT_LOADED;
	
	//
	// Update the timestamp
	//
	m_ullLastLoadAttemptTime = ullRightNow;
 
	//
	// See if the ASIC is present and working - only if the DSP is already loaded
	//	
	if (NULL != m_pwDspCode)
	{
		dwReturn = CheckAsicStatus();
		if (TRUE == dwReturn)
			return ECHOSTATUS_OK;
		
		//
		// ASIC check failed; force the DSP to reload
		//	
		m_pwDspCode = NULL;
	}
 
	//
	// Try and load the DSP
	//
	dwReturn = LoadDSP( m_pwDspCodeToLoad );
	if ( 	(ECHOSTATUS_OK != dwReturn) && 
			(ECHOSTATUS_FIRMWARE_LOADED != dwReturn) )
	{
		return dwReturn;
	}
	
	ECHO_DEBUGPRINTF(("DSP load OK\n"));
 
	//
	// Load the ASIC if the DSP load succeeded; LoadASIC will
	// always return TRUE for cards that don't have an ASIC.
	//
	dwReturn = LoadASIC();
	if ( FALSE == dwReturn )
	{
		dwReturn = ECHOSTATUS_ASIC_NOT_LOADED;
	}
	else
	{
		//
		// ASIC load was successful
		//
		RestoreDspSettings();
 
		dwReturn = ECHOSTATUS_OK;
	}
	
	return dwReturn;
	
}	// BOOL CDspCommObject::LoadFirmware()
 
 
//===========================================================================
//
// This function is used to read back the serial number from the DSP;
// this is triggered by the SET_COMMPAGE_ADDR command.
//
// Only some early Echogals products have serial numbers in the ROM;
// the serial number is not used, but you still need to do this as
// part of the DSP load process.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::ReadSn()
{
	INT32			j;
	DWORD			dwSn[ 6 ];
	ECHOSTATUS	Status;
 
	ECHO_DEBUGPRINTF( ("CDspCommObject::ReadSn\n") );
	for ( j = 0; j < 5; j++ )
	{
		Status = Read_DSP( &dwSn[ j ] );
		if ( Status != 0 )
		{
			ECHO_DEBUGPRINTF( ("\tFailed to read serial number word %ld\n",
									 j) );
			return ECHOSTATUS_DSP_DEAD;
		}
	}
	ECHO_DEBUGPRINTF( ("\tRead serial number %08lx %08lx %08lx %08lx %08lx\n",
							 dwSn[0], dwSn[1], dwSn[2], dwSn[3], dwSn[4]) );
	return ECHOSTATUS_OK;
	
}	// DWORD	CDspCommObject::ReadSn
 
 
 
 
//===========================================================================
//
//	This is called after LoadFirmware to restore old gains, meters on, 
// monitors, etc.
//
//===========================================================================
 
void CDspCommObject::RestoreDspSettings()
{
	ECHO_DEBUGPRINTF(("RestoreDspSettings\n"));
	ECHO_DEBUGPRINTF(("\tControl reg is 0x%lx\n",SWAP(m_pDspCommPage->dwControlReg) ));
 
	if ( !CheckAsicStatus() )
		return;
 
	m_pDspCommPage->dwHandshake = 0xffffffff;		
	
#ifdef MIDI_SUPPORT
	m_ullNextMidiWriteTime = 0;
#endif
	
	SetSampleRate();
	if ( 0 != m_wMeterOnCount )
	{
		SendVector( DSP_VC_METERS_ON );
	}
 
	SetInputClock( m_wInputClock );
	SetOutputClock( m_wOutputClock );
		
	if ( !WaitForHandshake() )
	{
		return;
	}
	UpdateAudioOutLineLevel();
 
	if ( !WaitForHandshake() )
		return;
	UpdateAudioInLineLevel();
 
	if ( HasVmixer() )
	{
		if ( !WaitForHandshake() )
			return;
		UpdateVmixerLevel();
	}
	
	if ( !WaitForHandshake() )
		return;
 
	ClearHandshake();
	SendVector( DSP_VC_UPDATE_FLAGS );
	
	ECHO_DEBUGPRINTF(("RestoreDspSettings done\n"));	
	
}	// void CDspCommObject::RestoreDspSettings()
 
 
 
 
/****************************************************************************
 
	DSP utilities
 
 ****************************************************************************/
 
//===========================================================================
//
// Write_DSP writes a 32-bit value to the DSP; this is used almost 
// exclusively for loading the DSP.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::Write_DSP
(
	DWORD dwData				// 32 bit value to write to DSP data register
)
{
	DWORD 		dwStatus;
	ULONGLONG 	ullCurTime, ullTimeout;
 
//	ECHO_DEBUGPRINTF(("Write_DSP\n"));
	
	m_pOsSupport->OsGetSystemTime( &ullCurTime );
	ullTimeout = ullCurTime + 10000000L;		// 10 sec.
	while ( ullTimeout >= ullCurTime ) 
	{
		dwStatus = GetDspRegister( CHI32_STATUS_REG );
		if ( ( dwStatus & CHI32_STATUS_HOST_WRITE_EMPTY ) != 0 )
		{
			SetDspRegister( CHI32_DATA_REG, dwData );
//			ECHO_DEBUGPRINTF(("Write DSP: 0x%x", dwData));
			return ECHOSTATUS_OK;
		}
		m_pOsSupport->OsGetSystemTime( &ullCurTime );
	}
 
	m_bBadBoard = TRUE;		// Set TRUE until DSP re-loaded
	
	ECHO_DEBUGPRINTF(("CDspCommObject::Write_DSP  Set m_bBadBoard to TRUE\n"));
		
	return ECHOSTATUS_DSP_TIMEOUT;
	
}	// ECHOSTATUS CDspCommObject::Write_DSP
 
 
 
 
//===========================================================================
//
// Read_DSP reads a 32-bit value from the DSP; this is used almost 
// exclusively for loading the DSP and checking the status of the ASIC.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::Read_DSP
(
	DWORD *pdwData				// Ptr to 32 bit value read from DSP data register
)
{
	DWORD 		dwStatus;
	ULONGLONG	ullCurTime, ullTimeout;
 
//	ECHO_DEBUGPRINTF(("Read_DSP\n"));
	m_pOsSupport->OsGetSystemTime( &ullCurTime );
 
	ullTimeout = ullCurTime + READ_DSP_TIMEOUT;	
	while ( ullTimeout >= ullCurTime )
	{
		dwStatus = GetDspRegister( CHI32_STATUS_REG );
		if ( ( dwStatus & CHI32_STATUS_HOST_READ_FULL ) != 0 )
		{
			*pdwData = GetDspRegister( CHI32_DATA_REG );
//			ECHO_DEBUGPRINTF(("Read DSP: 0x%x\n", *pdwData));
			return ECHOSTATUS_OK;
		}
		m_pOsSupport->OsGetSystemTime( &ullCurTime );
	}
 
	m_bBadBoard = TRUE;		// Set TRUE until DSP re-loaded
	
	ECHO_DEBUGPRINTF(("CDspCommObject::Read_DSP  Set m_bBadBoard to TRUE\n"));	
	
	return ECHOSTATUS_DSP_TIMEOUT;
}	// ECHOSTATUS CDspCommObject::Read_DSP
 
 
 
//===========================================================================
//
// Much of the interaction between the DSP and the driver is done via vector
// commands; SendVector writes a vector command to the DSP.  Typically,
// this causes the DSP to read or write fields in the comm page.
//
// Returns ECHOSTATUS_OK if sent OK.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::SendVector
(
	DWORD dwCommand				// 32 bit command to send to DSP vector register
)
{
	ULONGLONG	ullTimeout;
	ULONGLONG	ullCurTime;
 
//
// Turn this on if you want to see debug prints for every vector command
//
#if 0
//#ifdef ECHO_DEBUG
	char *	pszCmd;
	switch ( dwCommand )
	{
		case DSP_VC_ACK_INT :
			pszCmd = "DSP_VC_ACK_INT";
			break;
		case DSP_VC_SET_VMIXER_GAIN :
			pszCmd = "DSP_VC_SET_VMIXER_GAIN";
			break;
		case DSP_VC_START_TRANSFER :
			pszCmd = "DSP_VC_START_TRANSFER";
			break;
		case DSP_VC_METERS_ON :
			pszCmd = "DSP_VC_METERS_ON";
			break;
		case DSP_VC_METERS_OFF :
			pszCmd = "DSP_VC_METERS_OFF";
			break;
		case DSP_VC_UPDATE_OUTVOL :
			pszCmd = "DSP_VC_UPDATE_OUTVOL";
			break;
		case DSP_VC_UPDATE_INGAIN :
			pszCmd = "DSP_VC_UPDATE_INGAIN";
			break;
		case DSP_VC_ADD_AUDIO_BUFFER :
			pszCmd = "DSP_VC_ADD_AUDIO_BUFFER";
			break;
		case DSP_VC_TEST_ASIC :
			pszCmd = "DSP_VC_TEST_ASIC";
			break;
		case DSP_VC_UPDATE_CLOCKS :
			pszCmd = "DSP_VC_UPDATE_CLOCKS";
			break;
		case DSP_VC_SET_LAYLA_SAMPLE_RATE :
			if ( GetCardType() == LAYLA )
				pszCmd = "DSP_VC_SET_LAYLA_RATE";
			else if ( GetCardType() == GINA || GetCardType() == DARLA )
				pszCmd = "DSP_VC_SET_GD_AUDIO_STATE";
			else
				pszCmd = "DSP_VC_WRITE_CONTROL_REG";
			break;
		case DSP_VC_MIDI_WRITE :
			pszCmd = "DSP_VC_MIDI_WRITE";
			break;
		case DSP_VC_STOP_TRANSFER :
			pszCmd = "DSP_VC_STOP_TRANSFER";
			break;
		case DSP_VC_UPDATE_FLAGS :
			pszCmd = "DSP_VC_UPDATE_FLAGS";
			break;
		case DSP_VC_RESET :
			pszCmd = "DSP_VC_RESET";
			break;
		default :
			pszCmd = "?????";
			break;
	}
 
	ECHO_DEBUGPRINTF( ("SendVector: %s dwCommand %s (0x%x)\n",
								GetCardName(),
								pszCmd,
								dwCommand) );				
#endif
 
	m_pOsSupport->OsGetSystemTime( &ullCurTime );
	ullTimeout = ullCurTime + 100000L;		// 100m.s.
 
	//
	// Wait for the "vector busy" bit to be off
	//
	while ( ullCurTime <= ullTimeout) 
	{
		DWORD dwReg;
 
		dwReg = GetDspRegister( CHI32_VECTOR_REG );
		if ( 0 == (dwReg & CHI32_VECTOR_BUSY) )
		{
			SetDspRegister( CHI32_VECTOR_REG, dwCommand );
			
			return ECHOSTATUS_OK;
		}
		m_pOsSupport->OsGetSystemTime( &ullCurTime );
	}
 
	ECHO_DEBUGPRINTF( ("\tPunked out on SendVector\n") );
	ECHO_DEBUGBREAK();
	return ECHOSTATUS_DSP_TIMEOUT;
	
}	// ECHOSTATUS CDspCommObject::SendVector
 
 
 
//===========================================================================
//
//	Some vector commands involve the DSP reading or writing data to and
// from the comm page; if you send one of these commands to the DSP,
// it will complete the command and then write a non-zero value to
// the dwHandshake field in the comm page.  This function waits for the 
// handshake to show up.
//
//===========================================================================
 
BOOL CDspCommObject::WaitForHandshake()
{
	ULONGLONG ullDelta;
	ULONGLONG ullStartTime,ullTime;
	
	//
	// Wait up to three milliseconds for the handshake from the DSP 
	//
	m_pOsSupport->OsGetSystemTime( &ullStartTime );
	do
	{
		// Look for the handshake value
		if ( 0 != GetHandshakeFlag() )
		{
			return TRUE;
		}
 
		// Give the DSP time to access the comm page
		m_pOsSupport->OsSnooze( 2 );
		
		m_pOsSupport->OsGetSystemTime(&ullTime);
		ullDelta = ullTime - ullStartTime;
	} while (ullDelta < (ULONGLONG) HANDSHAKE_TIMEOUT);
 
	ECHO_DEBUGPRINTF( ("CDspCommObject::WaitForHandshake: Timeout waiting "
								"for DSP\n") );
	ECHO_DEBUGBREAK();
	return FALSE;
	
}		// DWORD	CDspCommObject::WaitForHandshake()
 
 
 
 
/****************************************************************************
 
	Transport methods
 
 ****************************************************************************/
 
//===========================================================================
//
// StartTransport starts transport for a set of pipes
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::StartTransport
(
	PCChannelMask	pChannelMask			// Pipes to start
)
{
	ECHO_DEBUGPRINTF( ("StartTransport\n") );
 
	//
	// Wait for the previous command to complete
	//
	if ( !WaitForHandshake() )
		return ECHOSTATUS_DSP_DEAD;
 
	//
	// Write the appropriate fields in the comm page
	//
	m_pDspCommPage->cmdStart.Clear();
	m_pDspCommPage->cmdStart = *pChannelMask;
	if ( !m_pDspCommPage->cmdStart.IsEmpty() )
	{
		//
		// Clear the handshake and send the vector command
		//
		ClearHandshake();
		SendVector( DSP_VC_START_TRANSFER );
 
		//
		// Keep track of which pipes are transporting
		//
		m_cmActive += *pChannelMask;
 
		return ECHOSTATUS_OK;
	}		// if this monkey is being started
 
	ECHO_DEBUGPRINTF( ("CDspCommObject::StartTransport: No pipes to start!\n") );
	return ECHOSTATUS_INVALID_CHANNEL;
	
}	// ECHOSTATUS CDspCommObject::StartTransport
 
 
//===========================================================================
//
// StopTransport pauses transport for a set of pipes
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::StopTransport
(
	PCChannelMask	pChannelMask
)
{
	ECHO_DEBUGPRINTF(("StopTransport\n"));
 
	//
	// Wait for the last command to finish
	//
	if ( !WaitForHandshake() )
		return ECHOSTATUS_DSP_DEAD;
 
	//
	// Write to the comm page
	//
	m_pDspCommPage->cmdStop.Clear();
	m_pDspCommPage->cmdStop = *pChannelMask;
	m_pDspCommPage->cmdReset.Clear();
	if ( !m_pDspCommPage->cmdStop.IsEmpty() )
	{
		//
		// Clear the handshake and send the vector command
		//
		ClearHandshake();
		SendVector( DSP_VC_STOP_TRANSFER );
 
		//
		// Keep track of which pipes are transporting
		//
		m_cmActive -= *pChannelMask;
 
		return ECHOSTATUS_OK;
	}		// if this monkey is being started
 
	ECHO_DEBUGPRINTF( ("CDspCommObject::StopTransport: No pipes to stop!\n") );
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CDspCommObject::StopTransport
 
 
//===========================================================================
//
// ResetTransport resets transport for a set of pipes
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::ResetTransport
(
	PCChannelMask	pChannelMask
)
{
	ECHO_DEBUGPRINTF(("ResetTransport\n"));
 
	//
	// Wait for the last command to finish
	//
	if ( !WaitForHandshake() )
		return ECHOSTATUS_DSP_DEAD;
 
	//
	// Write to the comm page
	//
	m_pDspCommPage->cmdStop.Clear();
	m_pDspCommPage->cmdReset.Clear();
	m_pDspCommPage->cmdStop = *pChannelMask;
	m_pDspCommPage->cmdReset = *pChannelMask;
	if ( !m_pDspCommPage->cmdReset.IsEmpty() )
	{
		//
		// Clear the handshake and send the vector command
		//
		ClearHandshake();
		SendVector( DSP_VC_STOP_TRANSFER );
 
		//
		// Keep track of which pipes are transporting
		//
		m_cmActive -= *pChannelMask;
 
		return ECHOSTATUS_OK;
	}		// if this monkey is being started
 
	ECHO_DEBUGPRINTF( ("CDspCommObject::ResetTransport: No pipes to reset!\n") );
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CDspCommObject::ResetTransport
 
 
//===========================================================================
//
// This tells the DSP where to start reading the scatter-gather list
// for a given pipe.
//
//===========================================================================
 
void CDspCommObject::SetAudioDuckListPhys
(
	WORD	wPipeIndex,			// Pipe index
	DWORD dwNewPhysAdr		// Physical address asserted on the PCI bus
)
{
	if (wPipeIndex < GetNumPipes() )
	{
		m_pDspCommPage->DuckListPhys[ wPipeIndex ].PhysAddr = 
																		SWAP( dwNewPhysAdr );
	}
}	// void CDspCommObject::SetAudioDuckListPhys
 
 
 
//===========================================================================
//
// Get a mask with active pipes
//
//===========================================================================
 
void CDspCommObject::GetActivePipes
(
	PCChannelMask	pChannelMask
)
{
	pChannelMask->Clear();
	*pChannelMask += m_cmActive;
}	// void CDspCommObject::GetActivePipes()
 
 
//===========================================================================
//
//	Set the audio format for a pipe
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::SetAudioFormat
(
	WORD 							wPipeIndex,
	PECHOGALS_AUDIOFORMAT	pFormat
)
{
	WORD wDspFormat = DSP_AUDIOFORM_SS_16LE;
	
	ECHO_DEBUGPRINTF(("CDspCommObject::SetAudioFormat - pipe %d  bps %d  channels %d\n",
							wPipeIndex,pFormat->wBitsPerSample,pFormat->wDataInterleave));
 
	//
	// Check the pipe number
	//
	if (wPipeIndex >= GetNumPipes() )
	{
		ECHO_DEBUGPRINTF( ("CDspCommObject::SetAudioFormat: Invalid pipe"
								 "%d\n",
								 wPipeIndex) );
		return ECHOSTATUS_INVALID_CHANNEL;
	}
 
	//
	// Look for super-interleave
	//
	if (pFormat->wDataInterleave > 2)
	{
		switch (pFormat->wBitsPerSample)
		{
			case 16 :
				wDspFormat = DSP_AUDIOFORM_SUPER_INTERLEAVE_16LE;
				break;
				
			case 24 :
				wDspFormat = DSP_AUDIOFORM_SUPER_INTERLEAVE_24LE;
				break;
				
			case 32 :
				wDspFormat = DSP_AUDIOFORM_SUPER_INTERLEAVE_32LE;
				break;
		}
		
		wDspFormat |= pFormat->wDataInterleave;
	}
	else
	{
		//
		// For big-endian data, only 32 bit mono->mono samples and 32 bit stereo->stereo
		// are supported
		//
		if (pFormat->byDataAreBigEndian)
		{
			
			switch ( pFormat->wDataInterleave )
			{
				case 1 :
					wDspFormat = DSP_AUDIOFORM_MM_32BE;
					break;
					
#ifdef STEREO_BIG_ENDIAN32_SUPPORT
				case 2 :
					wDspFormat = DSP_AUDIOFORM_SS_32BE;
					break;
#endif
 
			}
		}
		else
		{
			//
			// Check for 32 bit little-endian mono->mono case
			//
			if ( 	(1 == pFormat->wDataInterleave) &&
					(32 == pFormat->wBitsPerSample) &&
					(0 == pFormat->byMonoToStereo) )
			{
				wDspFormat = DSP_AUDIOFORM_MM_32LE;
			}
			else
			{
				//
				// Handle the other little-endian formats
				//
				switch (pFormat->wBitsPerSample)
				{
					case 8 :
						if (2 == pFormat->wDataInterleave)
							wDspFormat = DSP_AUDIOFORM_SS_8;
						else
							wDspFormat = DSP_AUDIOFORM_MS_8;
 
						break;
				
					default :		
					case 16 :
						if (2 == pFormat->wDataInterleave)
							wDspFormat = DSP_AUDIOFORM_SS_16LE;
						else
							wDspFormat = DSP_AUDIOFORM_MS_16LE;
						break;	
					
					case 24 :
						if (2 == pFormat->wDataInterleave)
							wDspFormat = DSP_AUDIOFORM_SS_24LE;
						else
							wDspFormat = DSP_AUDIOFORM_MS_24LE;
						break;					
					
					case 32 :
						if (2 == pFormat->wDataInterleave)
							wDspFormat = DSP_AUDIOFORM_SS_32LE;
						else
							wDspFormat = DSP_AUDIOFORM_MS_32LE;
						break;					
				}
				
			} // check other little-endian formats
		
		} // not big endian data
		
	} // not super-interleave
	
	m_pDspCommPage->wAudioFormat[wPipeIndex] = SWAP( wDspFormat );	
	
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CDspCommObject::SetAudioFormat
 
 
//===========================================================================
//
//	Get the audio format for a pipe
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::GetAudioFormat
( 
	WORD 							wPipeIndex,
	PECHOGALS_AUDIOFORMAT	pFormat
)
{
	if (wPipeIndex >= GetNumPipes() )
	{
		ECHO_DEBUGPRINTF( ("CDspCommObject::GetAudioFormat: Invalid pipe %d\n",
								 wPipeIndex) );
 
		return ECHOSTATUS_INVALID_CHANNEL;
	}
 
	pFormat->byDataAreBigEndian = 0;	// true for most of the formats
	pFormat->byMonoToStereo = 0;
	
	switch (SWAP(m_pDspCommPage->wAudioFormat[wPipeIndex]))
	{
		case DSP_AUDIOFORM_MS_8 :
			pFormat->wDataInterleave = 1;
			pFormat->wBitsPerSample = 8;
			pFormat->byMonoToStereo = 1;
			break;
			
		case DSP_AUDIOFORM_MS_16LE :
			pFormat->wDataInterleave = 1;
			pFormat->wBitsPerSample = 16;
			pFormat->byMonoToStereo = 1;			
			break;
			
		case DSP_AUDIOFORM_SS_8 :
			pFormat->wDataInterleave = 2;
			pFormat->wBitsPerSample = 8;
			break;
 
		case DSP_AUDIOFORM_SS_16LE :
			pFormat->wDataInterleave = 2;
			pFormat->wBitsPerSample = 16;
			break;
		
		case DSP_AUDIOFORM_SS_32LE :
			pFormat->wDataInterleave = 2;
			pFormat->wBitsPerSample = 32;
			break;			
		
		case DSP_AUDIOFORM_MS_32LE :
			pFormat->byMonoToStereo = 1;			
			// fall through
 
		case DSP_AUDIOFORM_MM_32LE :
			pFormat->wDataInterleave = 1;
			pFormat->wBitsPerSample = 32;
			break;			
			
		case DSP_AUDIOFORM_MM_32BE :
			pFormat->wDataInterleave = 1;
			pFormat->wBitsPerSample = 32;
			pFormat->byDataAreBigEndian = 1;
			break;			
			
		case DSP_AUDIOFORM_SS_32BE :
			pFormat->wDataInterleave = 2;
			pFormat->wBitsPerSample = 32;
			pFormat->byDataAreBigEndian = 1;
			break;			
		
	}
	
	return ECHOSTATUS_OK;
	
}	// void CDspCommObject::GetAudioFormat
 
 
 
/****************************************************************************
 
	Mixer methods
 
 ****************************************************************************/
 
//===========================================================================
//
// SetPipeOutGain - set the gain for a single output pipe
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::SetPipeOutGain
( 
	WORD 	wPipeOut, 
	WORD	wBusOut,
	INT32	iGain,
	BOOL 	fImmediate
)
{
	if ( wPipeOut < m_wNumPipesOut )
	{
		//
		// Wait for the handshake
		//
		if ( !WaitForHandshake() )
			return ECHOSTATUS_DSP_DEAD;
			
		//
		// Save the new value
		//
		iGain = GENERIC_TO_DSP(iGain);
		m_pDspCommPage->OutLineLevel[ wPipeOut ] = (BYTE) iGain;
 
		/*
		ECHO_DEBUGPRINTF( ("CDspCommObject::SetPipeOutGain: Out pipe %d "
								 "= 0x%lx\n",
								 wPipeOut,
								 iGain) );
		*/
 
		//
		// If fImmediate is true, then do the gain setting right now.
		// If you want to do a batch of gain settings all at once, it's
		// more efficient to call this several times and then only set
		// fImmediate for the last one; then the DSP picks up all of
		// them at once.
		//								 
		if (fImmediate)
		{
			return UpdateAudioOutLineLevel();
		}
 
		return ECHOSTATUS_OK;		
 
	}
 
	ECHO_DEBUGPRINTF( ("CDspCommObject::SetPipeOutGain: Invalid out pipe "
							 "%d\n",
							 wPipeOut) );
	ECHO_DEBUGBREAK();	 
							 
	return ECHOSTATUS_INVALID_CHANNEL;
	
}	// SetPipeOutGain
 
 
//===========================================================================
//
// GetPipeOutGain returns the current gain for an output pipe.  This isn't
// really used as the mixer code in CEchoGals stores logical values for
// these, but it's here for completeness.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::GetPipeOutGain
( 
	WORD 	wPipeOut, 
	WORD 	wBusOut,
	INT32 &iGain
)
{
	if (wPipeOut < m_wNumPipesOut)
	{
		iGain = (INT32) (char) m_pDspCommPage->OutLineLevel[ wPipeOut ];
		iGain = DSP_TO_GENERIC(8);
		return ECHOSTATUS_OK;		
	}
 
	ECHO_DEBUGPRINTF( ("CDspCommObject::GetPipeOutGain: Invalid out pipe "
							 "%d\n",
							 wPipeOut) );
							 
	return ECHOSTATUS_INVALID_CHANNEL;
	
}	// GetPipeOutGain
 
	
 
//===========================================================================
//
// Set input bus gain - iGain is in units of 0.5 dB
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::SetBusInGain( WORD wBusIn, INT32 iGain)
{
	if (wBusIn > m_wNumBussesIn)
		return ECHOSTATUS_INVALID_CHANNEL;
 
	//
	// Wait for the handshake (OK even if ASIC is not loaded)
	//
	if ( !WaitForHandshake() )
		return ECHOSTATUS_DSP_DEAD;
		
	//
	// Adjust the gain value
	//		
	iGain += GL20_INPUT_GAIN_MAGIC_NUMBER;
	
	//
	// Put it in the comm page
	//
	m_pDspCommPage->InLineLevel[wBusIn] = (BYTE) iGain;
 
	return UpdateAudioInLineLevel();
}	
 
 
//===========================================================================
//
// Get the input bus gain in units of 0.5 dB
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::GetBusInGain( WORD wBusIn, INT32 &iGain)
{
	if (wBusIn > m_wNumBussesIn)
		return ECHOSTATUS_INVALID_CHANNEL;
		
	iGain = m_pDspCommPage->InLineLevel[wBusIn];
	iGain -= GL20_INPUT_GAIN_MAGIC_NUMBER;
 
	return ECHOSTATUS_OK;
}	
 
 
//===========================================================================
//
//	Set the nominal level for an input or output bus
//
// bState TRUE			-10 nominal level
// bState FALSE		+4 nominal level
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::SetNominalLevel
(
	WORD	wBus,
	BOOL	bState
)
{
	//
	// Check the pipe index
	//
	if (wBus < (m_wNumBussesOut + m_wNumBussesIn))
	{
		//
		// Wait for the handshake (OK even if ASIC is not loaded)
		//
		if ( !WaitForHandshake() )
			return ECHOSTATUS_DSP_DEAD;
 
		//
		// Set the nominal bit
		//		
		if ( bState )
			m_pDspCommPage->cmdNominalLevel.SetIndexInMask( wBus );
		else
			m_pDspCommPage->cmdNominalLevel.ClearIndexInMask( wBus );
 
		return UpdateAudioOutLineLevel();
	}
 
	ECHO_DEBUGPRINTF( ("CDspCommObject::SetNominalOutLineLevel Invalid "
							 "index %d\n",
							 wBus ) );
	return ECHOSTATUS_INVALID_CHANNEL;
 
}	// ECHOSTATUS CDspCommObject::SetNominalLevel
 
 
//===========================================================================
//
//	Get the nominal level for an input or output bus
//
// bState TRUE			-10 nominal level
// bState FALSE		+4 nominal level
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::GetNominalLevel
(
	WORD	wBus,
	PBYTE pbyState
)
{
 
	if (wBus < (m_wNumBussesOut + m_wNumBussesIn))
	{
		*pbyState = (BYTE)
			m_pDspCommPage->cmdNominalLevel.TestIndexInMask( wBus );
		return ECHOSTATUS_OK;
	}
 
	ECHO_DEBUGPRINTF( ("CDspCommObject::GetNominalLevel Invalid "
							 "index %d\n",
							 wBus ) );
	return ECHOSTATUS_INVALID_CHANNEL;
}	// ECHOSTATUS CDspCommObject::GetNominalLevel
 
 
//===========================================================================
//
//	Set the monitor level from an input bus to an output bus.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::SetAudioMonitor
(
	WORD	wBusOut,	// output bus
	WORD	wBusIn,	// input bus
	INT32	iGain,
	BOOL 	fImmediate
)
{
	/*
	ECHO_DEBUGPRINTF( ("CDspCommObject::SetAudioMonitor: "
							 "Out %d in %d Gain %d (0x%x)\n",
							 wBusOut, wBusIn, iGain, iGain) );
	*/
 
	//
	// The monitor array is a one-dimensional array;
	// compute the offset into the array
	//
	WORD	wOffset = ComputeAudioMonitorIndex( wBusOut, wBusIn );
 
	//
	// Wait for the offset
	//	
	if ( !WaitForHandshake() )
		return ECHOSTATUS_DSP_DEAD;
 
	//
	// Write the gain value to the comm page
	//
	iGain = GENERIC_TO_DSP(iGain);
	m_pDspCommPage->byMonitors[ wOffset ] = (BYTE) (iGain);
	
	//
	// If fImmediate is set, do the command right now
	//
	if (fImmediate)
	{
		return UpdateAudioOutLineLevel();
	}
	
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CDspCommObject::SetAudioMonitor
 
 
//===========================================================================
//
// SetMetersOn turns the meters on or off.  If meters are turned on, the
// DSP will write the meter and clock detect values to the comm page
// at about 30 Hz.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::SetMetersOn
(
	BOOL bOn
)
{
	if ( bOn )
	{
		if ( 0 == m_wMeterOnCount )
		{
			SendVector( DSP_VC_METERS_ON );
		}
		m_wMeterOnCount++;
	}
	else
	{
		INT32	iDevice;
	
		if ( m_wMeterOnCount == 0 )
			return ECHOSTATUS_OK;
 
		if ( 0 == --m_wMeterOnCount )
		{
			SendVector( DSP_VC_METERS_OFF );
			
			for ( iDevice = 0; iDevice < DSP_MAXPIPES; iDevice++ )
			{
				BYTE muted;
 
				muted = (BYTE) GENERIC_TO_DSP(ECHOGAIN_MUTED);
				m_pDspCommPage->VUMeter[ iDevice ]   = muted;
				m_pDspCommPage->PeakMeter[ iDevice ] = muted;
			}
		}
	}
	return ECHOSTATUS_OK;
	
}	// ECHOSTATUS CDspCommObject::SetMetersOn
 
 
//===========================================================================
//
// Tell the DSP to read and update output, nominal & monitor levels 
//	in comm page.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::UpdateAudioOutLineLevel()
{
	//ECHO_DEBUGPRINTF( ( "CDspCommObject::UpdateAudioOutLineLevel:\n" ) );
	
	if (FALSE == m_bASICLoaded)
		return ECHOSTATUS_ASIC_NOT_LOADED;
	
	ClearHandshake();
	return( SendVector( DSP_VC_UPDATE_OUTVOL ) );
	
}	// ECHOSTATUS CDspCommObject::UpdateAudioOutLineLevel()
 
 
//===========================================================================
//
// Tell the DSP to read and update input levels in comm page
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::UpdateAudioInLineLevel()
{
	//ECHO_DEBUGPRINTF( ( "CDspCommObject::UpdateAudioInLineLevel:\n" ) );
	
	if (FALSE == m_bASICLoaded)
		return ECHOSTATUS_ASIC_NOT_LOADED;
 
	ClearHandshake();
	return( SendVector( DSP_VC_UPDATE_INGAIN ) );
}		// ECHOSTATUS CDspCommObject::UpdateAudioInLineLevel()
 
 
//===========================================================================
//
// Tell the DSP to read and update virtual mixer levels 
//	in comm page.  This method is overridden by cards that actually 
// support a vmixer.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::UpdateVmixerLevel()
{
	ECHO_DEBUGPRINTF(("CDspCommObject::UpdateVmixerLevel\n"));
	return ECHOSTATUS_NOT_SUPPORTED;
}	// ECHOSTATUS CDspCommObject::UpdateVmixerLevel()
 
 
//===========================================================================
//
// Tell the DSP to change the input clock
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::SetInputClock(WORD wClock)
{
	//
	// Wait for the last command
	//
	if (!WaitForHandshake())
		return ECHOSTATUS_DSP_DEAD;
 
	ECHO_DEBUGPRINTF( ("CDspCommObject::SetInputClock:\n") );		
		
	//
	// Write to the comm page
	//
	m_pDspCommPage->wInputClock = SWAP(wClock);
	
	//
	// Clear the handshake and send the command
	//
	ClearHandshake();
	ECHOSTATUS Status = SendVector(DSP_VC_UPDATE_CLOCKS);
	
	return Status;
 
}	// ECHOSTATUS CDspCommObject::SetInputClock
 
 
//===========================================================================
//
// Tell the DSP to change the output clock - Layla20 only
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::SetOutputClock(WORD wClock)
{
 
	return ECHOSTATUS_CLOCK_NOT_SUPPORTED;
	
}	// ECHOSTATUS CDspCommObject::SetOutputClock
 
 
//===========================================================================
//
// Fill out an ECHOGALS_METERS struct using the current values in the 
// comm page.  This method is overridden for vmixer cards.
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::GetAudioMeters
(
	PECHOGALS_METERS	pMeters
)
{
	pMeters->iNumPipesOut = 0;
	pMeters->iNumPipesIn = 0;
 
	//
	//	Output 
	// 
	DWORD dwCh = 0;
	WORD 	i;
 
	pMeters->iNumBussesOut = (INT32) m_wNumBussesOut;
	for (i = 0; i < m_wNumBussesOut; i++)
	{
		pMeters->iBusOutVU[i] = 
			DSP_TO_GENERIC( ((INT32) (INT8) m_pDspCommPage->VUMeter[ dwCh ]) );
 
		pMeters->iBusOutPeak[i] = 
			DSP_TO_GENERIC( ((INT32) (INT8) m_pDspCommPage->PeakMeter[ dwCh ]) );
		
		dwCh++;
	}
 
	pMeters->iNumBussesIn = (INT32) m_wNumBussesIn;	
	for (i = 0; i < m_wNumBussesIn; i++)
	{
		pMeters->iBusInVU[i] = 
			DSP_TO_GENERIC( ((INT32) (INT8) m_pDspCommPage->VUMeter[ dwCh ]) );
		pMeters->iBusInPeak[i] = 
			DSP_TO_GENERIC( ((INT32) (INT8) m_pDspCommPage->PeakMeter[ dwCh ]) );
		
		dwCh++;
	}
	
	return ECHOSTATUS_OK;
	
} // GetAudioMeters
 
 
#ifdef DIGITAL_INPUT_AUTO_MUTE_SUPPORT
 
//===========================================================================
//
// Digital input auto-mute - Gina24, Layla24, and Mona only
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::GetDigitalInputAutoMute(BOOL &fAutoMute)
{
	fAutoMute = m_fDigitalInAutoMute;	
	
	ECHO_DEBUGPRINTF(("CDspCommObject::GetDigitalInputAutoMute %d\n",fAutoMute));
	
	return ECHOSTATUS_OK;
}
 
ECHOSTATUS CDspCommObject::SetDigitalInputAutoMute(BOOL fAutoMute)
{
	ECHO_DEBUGPRINTF(("CDspCommObject::SetDigitalInputAutoMute %d\n",fAutoMute));
	
	//
	// Store the flag
	//
	m_fDigitalInAutoMute = fAutoMute;
	
	//
	// Re-set the input clock to the current value - indirectly causes the 
	// auto-mute flag to be sent to the DSP
	//
	SetInputClock(m_wInputClock);
	
	return ECHOSTATUS_OK;
}
 
#endif // DIGITAL_INPUT_AUTO_MUTE_SUPPORT
 
 
 
 
/****************************************************************************
 
	Power management
 
 ****************************************************************************/
 
//===========================================================================
//
// Tell the DSP to go into low-power mode
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::GoComatose()
{
	ECHO_DEBUGPRINTF(("CDspCommObject::GoComatose\n"));
 
	if (NULL != m_pwDspCode)
	{
		//
		// Make LoadFirmware do a complete reload
		//	
		m_pwDspCode = NULL;
		
		//
		// Make sure that the sample rate get re-set on wakeup
		// (really only for Indigo and Mia)
		//
		m_pDspCommPage->dwControlReg = 0;
		
		//
		// Put the DSP to sleep
		//
		return SendVector(DSP_VC_GO_COMATOSE);
	}
	
	return ECHOSTATUS_OK;
 
}	// end of GoComatose
 
 
 
#ifdef MIDI_SUPPORT
 
/****************************************************************************
 
	MIDI
 
 ****************************************************************************/
 
//===========================================================================
//
// Send a buffer full of MIDI data to the DSP
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::WriteMidi
(
	PBYTE		pData,						// Ptr to data buffer
	DWORD		dwLength,					// How many bytes to write
	PDWORD	pdwActualCt					// Return how many actually written
)
{
	DWORD 		dwWriteCount,dwHandshake,dwStatus;
	BYTE			*pOutBuffer;
	
	
	//
	// Return immediately if the handshake flag is clar
	//
	dwHandshake = GetHandshakeFlag();
	if (0 == dwHandshake)
	{
		ECHO_DEBUGPRINTF(("CDCO::WriteMidi - can't write - handshake %ld\n",dwHandshake));
		
		*pdwActualCt = 0;
		return ECHOSTATUS_BUSY;
	}
	
	//
	// Return immediately if HF4 is clear - HF4 indicates that it is safe
	// to write MIDI output data
	//
	dwStatus = GetDspRegister( CHI32_STATUS_REG );
	if ( 0 == (dwStatus & CHI32_STATUS_REG_HF4 ) )
	{
		ECHO_DEBUGPRINTF(("CDCO::WriteMidi - can't write - dwStatus 0x%lx\n",dwStatus));
		
		*pdwActualCt = 0;
		return ECHOSTATUS_BUSY;
	}
	
	
	//
	// Copy data to the comm page; limit to the amount of space in the DSP output
	// FIFO and in the comm page
	//
	dwWriteCount = dwLength;
	if (dwWriteCount > (CP_MIDI_OUT_BUFFER_SIZE - 1))
	{
		dwWriteCount = CP_MIDI_OUT_BUFFER_SIZE - 1;
	}
 
	ECHO_DEBUGPRINTF(("WriteMidi - dwWriteCount %ld\n",dwWriteCount));
			
	*pdwActualCt = dwWriteCount;	// Save the # of bytes written for the caller
 
	pOutBuffer = m_pDspCommPage->byMidiOutData;
	*pOutBuffer = (BYTE) dwWriteCount;
	
	pOutBuffer++;
	
	OsCopyMemory(pOutBuffer,pData,dwWriteCount);
 
	//
	// Send the command to the DSP
	//	 
	ClearHandshake();
	m_pDspCommPage->dwMidiOutFreeCount = 0;
	SendVector( DSP_VC_MIDI_WRITE );
	
	//
	// Save the current time - used to detect if MIDI out is currently busy
	//
	ULONGLONG ullTime;
 
	m_pOsSupport->OsGetSystemTime( &ullTime );
	m_ullMidiOutTime = ullTime;
														
	return ECHOSTATUS_OK;
	
}		// ECHOSTATUS CDspCommObject::WriteMidi
 
 
//===========================================================================
//
// Called from the interrupt handler - get a MIDI input byte
//
//===========================================================================
 
ECHOSTATUS CDspCommObject::ReadMidi
(
	WORD 		wIndex,				// Buffer index
	DWORD &	dwData				// Return data
)
{
	if ( wIndex >= CP_MIDI_IN_BUFFER_SIZE )
		return ECHOSTATUS_INVALID_INDEX;
 
	//
	// Get the data
	//	
	dwData = SWAP( m_pDspCommPage->wMidiInData[ wIndex ] );
 
	//
	// Timestamp for the MIDI input activity indicator
	//
	ULONGLONG ullTime;
 
	m_pOsSupport->OsGetSystemTime( &ullTime );
	m_ullMidiInTime = ullTime;
 
	return ECHOSTATUS_OK;
	
}	// ECHOSTATUS CDspCommObject::ReadMidi
 
 
ECHOSTATUS CDspCommObject::SetMidiOn( BOOL bOn )
{
	if ( bOn )
	{
		if ( 0 == m_wMidiOnCount )
		{
			if ( !WaitForHandshake() )
				return ECHOSTATUS_DSP_DEAD;
 
			m_pDspCommPage->dwFlags |= SWAP( (DWORD) DSP_FLAG_MIDI_INPUT );
			
			ClearHandshake();
			SendVector( DSP_VC_UPDATE_FLAGS );
		}
		m_wMidiOnCount++;
	}
	else
	{
		if ( m_wMidiOnCount == 0 )
			return ECHOSTATUS_OK;
 
		if ( 0 == --m_wMidiOnCount )
		{
			if ( !WaitForHandshake() )
				return ECHOSTATUS_DSP_DEAD;
				
			m_pDspCommPage->dwFlags &= SWAP( (DWORD) ~DSP_FLAG_MIDI_INPUT );
			
			ClearHandshake();
			SendVector( DSP_VC_UPDATE_FLAGS );
		}
	}
 
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CDspCommObject::SetMidiOn
 
 
//===========================================================================
//
// Detect MIDI output activity
//
//===========================================================================
 
BOOL CDspCommObject::IsMidiOutActive()
{
	ULONGLONG	ullCurTime;
 
	m_pOsSupport->OsGetSystemTime( &ullCurTime );
	return( ( ( ullCurTime - m_ullMidiOutTime ) > MIDI_ACTIVITY_TIMEOUT_USEC ) ? FALSE : TRUE );
	
}	// BOOL CDspCommObject::IsMidiOutActive()
 
 
#endif // MIDI_SUPPORT
 
 
 
// **** CDspCommObject.cpp ****

V610 Undefined behavior. Check the shift operator '<<'. The left operand '-128' is negative.

V610 Undefined behavior. Check the shift operator '<<'. The left operand '-128' is negative.

V610 Undefined behavior. Check the shift operator '<<'. The left operand '-128' is negative.