// ****************************************************************************
//
//		CEchoGals_mixer.cpp
//
//		Implementation file for the CEchoGals driver class (mixer functions).
//		Set editor tabs to 3 for your viewing pleasure.
//
//		Copyright Echo Digital Audio Corporation (c) 1998 - 2002
//		All rights reserved
//		www.echoaudio.com
//		
//		Permission is hereby granted, free of charge, to any person obtaining a
//		copy of this software and associated documentation files (the
//		"Software"), to deal with the Software without restriction, including
//		without limitation the rights to use, copy, modify, merge, publish,
//		distribute, sublicense, and/or sell copies of the Software, and to
//		permit persons to whom the Software is furnished to do so, subject to
//		the following conditions:
//		
//		- Redistributions of source code must retain the above copyright
//		notice, this list of conditions and the following disclaimers.
//		
//		- Redistributions in binary form must reproduce the above copyright
//		notice, this list of conditions and the following disclaimers in the
//		documentation and/or other materials provided with the distribution.
//		
//		- Neither the name of Echo Digital Audio, nor the names of its
//		contributors may be used to endorse or promote products derived from
//		this Software without specific prior written permission.
//
//		THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//		EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//		MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
//		IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
//		ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
//		TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
//		SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
//
// ****************************************************************************

#include "CEchoGals.h"


/****************************************************************************

	CEchoGals mixer client management

 ****************************************************************************/

//===========================================================================
//
// Open the mixer - create a mixer client structure for this client and 
// return the cookie.  The cookie uniquely identifies this client to the 
// mixer driver. 
//
// Valid cookies are non-zero.  If you get a zero cookie, the open failed
// somehow.
//
// Clients can change mixer controls without calling OpenMixer first; it just
// means that they can't track control changes made by other clients.
//
//===========================================================================

ECHOSTATUS CEchoGals::OpenMixer(DWORD &dwCookie)
{
	if (m_fMixerDisabled)
		return ECHOSTATUS_MIXER_DISABLED;

	//
	// Allocate the mixer client structure
	//
	ECHO_MIXER_CLIENT *pClient = NULL;
	ECHOSTATUS			Status;
	
	Status = OsAllocateNonPaged(sizeof(ECHO_MIXER_CLIENT),(void **) &pClient);
	if (NULL == pClient)
	{
		dwCookie = 0;
		return Status;		
	}

	
	//
	// Make the cookie
	//
	ULONGLONG ullTime;

	m_pOsSupport->OsGetSystemTime(&ullTime);
	dwCookie = (DWORD) ullTime;
	if (0 == dwCookie)
		dwCookie = 1;
	
	//
	// Look through the existing mixer client list to ensure that this
	// cookie is unique.
	//
	ECHO_MIXER_CLIENT *pTemp;
	
	pTemp = m_pMixerClients;
	while (pTemp != NULL)
	{
		if (dwCookie == pTemp->dwCookie)
		{
			//
			// Oops, someone is already using this cookie.  Increment 
			// the new cookie and try again.
			//
			dwCookie++;
			pTemp = m_pMixerClients;			
		}
	
		pTemp = pTemp->pNext;
	}
	
	//
	// Store the new cookie and the new mixer client
	//
	pClient->dwCookie = dwCookie;
	pClient->pNext = m_pMixerClients;
	m_pMixerClients = pClient;
	
	return ECHOSTATUS_OK;
	
}	// OpenMixer


//===========================================================================
//
// Find a mixer client that matches a cookie
//
//===========================================================================

ECHO_MIXER_CLIENT *CEchoGals::GetMixerClient(DWORD dwCookie)
{
	ECHO_MIXER_CLIENT *pTemp;
	
	pTemp = m_pMixerClients;
	while (NULL != pTemp)
	{
		if (dwCookie == pTemp->dwCookie)
			break;
			
		pTemp = pTemp->pNext;
	}
	
	return pTemp;
	
}	// GetMixerClient


//===========================================================================
//
// Close the mixer - free the mixer client structure
//
//===========================================================================

ECHOSTATUS CEchoGals::CloseMixer(DWORD dwCookie)
{
	//
	//	Search the linked list and remove the client that matches the cookie
	// 
	ECHO_MIXER_CLIENT *pTemp;
	
	pTemp = m_pMixerClients;
	if (NULL == pTemp)
		return ECHOSTATUS_BAD_COOKIE;
		
	//
	// Head of the list?
	//		
	if (pTemp->dwCookie == dwCookie)
	{
		m_pMixerClients = pTemp->pNext;
		OsFreeNonPaged(pTemp);
		
		return ECHOSTATUS_OK;
	}
		
	//
	// Not the head of the list; search the list
	//
	while (NULL != pTemp->pNext)
	{
		if (dwCookie == pTemp->pNext->dwCookie)
		{
			ECHO_MIXER_CLIENT *pDeadClient;
			
			pDeadClient = pTemp->pNext;
			pTemp->pNext = pDeadClient->pNext;
			OsFreeNonPaged(pDeadClient);
			
			return ECHOSTATUS_OK;
		}
			
		pTemp = pTemp->pNext;
	}

	//
	// No soup for you!
	//
	return ECHOSTATUS_BAD_COOKIE;

} // CloseMixer	


//===========================================================================
//
// IsMixerOpen - returns true if at least one client has the mixer open
//
//===========================================================================

BOOL CEchoGals::IsMixerOpen()
{
	if (NULL == m_pMixerClients)
		return FALSE;
		
	return TRUE;
	
}	// IsMixerOpen


//===========================================================================
//
// This function is called when a mixer control changes; add the change
//	to the queue for each client.
//
// Here's what the wCh1 and wCh2 parameters represent, based on the wType
// parameter:
//
// wType				wCh1				wCh2
// -----				----				----
// ECHO_BUS_OUT	Output bus		Ignored
// ECHO_BUS_IN		Input bus		Ignored
// ECHO_PIPE_OUT	Output pipe		Output bus
// ECHO_MONITOR	Input bus		Output bus
//
// ECHO_PIPE_IN is not used right now.
//
//===========================================================================

ECHOSTATUS CEchoGals::MixerControlChanged
(
	WORD	wType,		// One of the ECHO_CHANNEL_TYPES
	WORD  wParameter, // One of the MXN_* values
	WORD 	wCh1,			// Depends on the wType value
	WORD  wCh2			// Also depends on wType value
)
{
	ECHO_MIXER_CLIENT *pClient = m_pMixerClients;
	PMIXER_NOTIFY		pNotify;
	
	if (m_fMixerDisabled)
		return ECHOSTATUS_MIXER_DISABLED;
	
	//
	// Go through all the clients and store this control change
	//
	while (NULL != pClient)
	{
		//
		// Search the circular buffer for this client and see if 
		// this control change is already stored
		//
		DWORD				dwIndex,dwCount;
		BOOL				fFound;
		
		dwCount = pClient->dwCount;
		dwIndex = pClient->dwTail;
		fFound = FALSE;
		while (dwCount > 0)
		{
			pNotify = pClient->Notifies + dwIndex;
			if (	(pNotify->wType == wType) &&
					(pNotify->wParameter == wParameter) &&
					(pNotify->u.wPipeOut == wCh1) && 	// can use any union member her
					(pNotify->wBusOut == wCh2))
			{
				//
				// Found this notify already in the circular buffer 
				//
				fFound = TRUE;
				break;
			}
			dwIndex++;
			dwIndex &= MAX_MIXER_NOTIFIES - 1;
			dwCount--;
		}
		
		//
		// If the notify was not found, add this notify to the circular buffer if 
		// there is enough room.
		//
		if ( 	(FALSE == fFound) &&
				(pClient->dwCount != MAX_MIXER_NOTIFIES))
		{
			pNotify = pClient->Notifies + pClient->dwHead;
			
			pNotify->wType 		= wType;
			pNotify->wParameter 	= wParameter;
			
			if (ECHO_BUS_OUT == wType)
			{
				pNotify->u.wPipeOut = ECHO_CHANNEL_UNUSED;
				pNotify->wBusOut = wCh1;
			}
			else
			{
				pNotify->u.wPipeOut	= wCh1;		// can use any union member here also
				pNotify->wBusOut	= wCh2;
			}
			
			pClient->dwCount += 1;
			pClient->dwHead = (pClient->dwHead + 1) & (MAX_MIXER_NOTIFIES - 1);
		}
		
		pClient = pClient->pNext;
	}
	
	return ECHOSTATUS_OK;
	
}	// MixerControlChanged


//===========================================================================
//
// This method is called when a client wants to know what controls have 
// changed.
//
//===========================================================================

ECHOSTATUS CEchoGals::GetControlChanges
(
	PMIXER_MULTI_NOTIFY	pNotifies
)
{
	//
	// Match the cookie
	//
	ECHO_MIXER_CLIENT *pClient = GetMixerClient(pNotifies->dwCookie);
	
	if (NULL == pClient)
	{
		pNotifies->dwCount = 0;
		return ECHOSTATUS_BAD_COOKIE;		
	}
	
	//
	// Copy mixer notifies
	//
	PMIXER_NOTIFY	pDest,pSrc;
	DWORD				dwNumClientNotifies,dwNumReturned;
	
	dwNumClientNotifies = pNotifies->dwCount;
	pDest = pNotifies->Notifies;
	dwNumReturned = 0;
	while ( (dwNumClientNotifies > 0) && (pClient->dwCount > 0))
	{
		pSrc = pClient->Notifies + pClient->dwTail;
	
		OsCopyMemory(pDest,pSrc,sizeof(MIXER_NOTIFY));
		
		pDest++;

		pClient->dwTail = (pClient->dwTail + 1) & (MAX_MIXER_NOTIFIES - 1);
		pClient->dwCount -= 1;

		dwNumClientNotifies--;

		dwNumReturned++;
	}
	
	pNotifies->dwCount = dwNumReturned;
	
	return ECHOSTATUS_OK;

}	// GetControlChanges




/****************************************************************************

	CEchoGals mixer control

 ****************************************************************************/

//===========================================================================
//
// Process mixer function - service a single mixer function
//
//===========================================================================

ECHOSTATUS CEchoGals::ProcessMixerFunction
(
	PMIXER_FUNCTION	pMixerFunction,
	INT32 &					iRtnDataSz
)
{
	ECHOSTATUS			Status = ECHOSTATUS_OK;
	
	if (m_fMixerDisabled)
		return ECHOSTATUS_MIXER_DISABLED;
	
	switch ( pMixerFunction->iFunction )
	{
		case MXF_GET_CAPS :
			Status = GetCapabilities( &pMixerFunction->Data.Capabilities );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_CAPS  Status %ld\n", Status) );
			break;

		case MXF_GET_LEVEL :
			Status = GetAudioLineLevel( pMixerFunction);
			/*
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_LEVEL  Status %ld\n", Status) );
			*/
			break;

		case MXF_SET_LEVEL :
			Status = SetAudioLineLevel( pMixerFunction);
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_LEVEL  Status %ld\n", Status) );
			break;

		case MXF_GET_NOMINAL :
			Status = GetAudioNominal( pMixerFunction);
			/*
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_NOMINAL  Status %ld\n", Status) );
			*/
			break;

		case MXF_SET_NOMINAL :
			Status = SetAudioNominal( pMixerFunction);
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_NOMINAL  Status %ld\n", Status) );
			break;

		case MXF_GET_MONITOR :
			Status = GetAudioMonitor( pMixerFunction->Channel.wChannel,
											  pMixerFunction->Data.Monitor.wBusOut,
											  pMixerFunction->Data.Monitor.Data.iLevel );
			/*	  
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_MONITOR  Status %ld\n", Status) );
			*/
			break;

		case MXF_SET_MONITOR :
			Status = SetAudioMonitor( pMixerFunction->Channel.wChannel,
											  pMixerFunction->Data.Monitor.wBusOut,
											  pMixerFunction->Data.Monitor.Data.iLevel );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_MONITOR  Status %ld\n", Status) );
			break;

		case MXF_GET_CLOCK_DETECT :
			Status = GetInputClockDetect( pMixerFunction->Data.dwClockDetectBits );
			break;
	
		case MXF_GET_INPUT_CLOCK :
			Status = GetInputClock( pMixerFunction->Data.wClock );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_INPUT_CLOCK  Status %ld\n", Status) );
			break;			

		case MXF_SET_INPUT_CLOCK :
			Status = SetInputClock( pMixerFunction->Data.wClock );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_INPUT_CLOCK  Status %ld\n", Status) );
			break;			


		case MXF_GET_OUTPUT_CLOCK :
			Status = GetOutputClock( pMixerFunction->Data.wClock );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_OUTPUT_CLOCK  Status %ld\n", Status) );
			break;			

		case MXF_SET_OUTPUT_CLOCK :
			Status = SetOutputClock( pMixerFunction->Data.wClock );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_OUTPUT_CLOCK  Status %ld\n", Status) );
			break;			


		case MXF_GET_METERS :
		
			if (NULL != GetDspCommObject())
			{
				Status = GetDspCommObject()->
								GetAudioMeters( &pMixerFunction->Data.Meters );				
			}
			else
			{
				Status = ECHOSTATUS_DSP_DEAD;
			}

			//ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
			// 						 "MXF_GET_METERS  Status %ld\n", Status) );
			break;

		case MXF_GET_METERS_ON :
			Status = GetMetersOn( pMixerFunction->Data.bMetersOn );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_METERS  Status %ld\n", Status) );
			break;

		case MXF_SET_METERS_ON :
			Status = SetMetersOn( pMixerFunction->Data.bMetersOn );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_METERS_ON  Status %ld\n", Status) );
			break;

		case MXF_GET_PROF_SPDIF :
			if ( ECHOSTATUS_DSP_DEAD == IsProfessionalSpdif() )
			{
				Status = ECHOSTATUS_DSP_DEAD;				
			}
			else
			{
				pMixerFunction->Data.bProfSpdif = IsProfessionalSpdif();
			}
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_PROF_SPDIF  Pro S/PDIF: 0x%x  Status %ld\n", 
									 pMixerFunction->Data.bProfSpdif,
									 Status) );
			break;

		case MXF_SET_PROF_SPDIF :
			SetProfessionalSpdif( pMixerFunction->Data.bProfSpdif );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_PROF_SPDIF  Pro S/PDIF: 0x%x  Status %ld\n", 
									 pMixerFunction->Data.bProfSpdif,
									 Status) );
			break;

		case MXF_GET_MUTE :
			Status = GetAudioMute(pMixerFunction);
			/*
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_MUTE  Status %ld\n", Status) );
			*/
			break;

		case MXF_SET_MUTE :
			Status = SetAudioMute(pMixerFunction);
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_MUTE  Status %ld\n", Status) );
			break;

		case MXF_GET_MONITOR_MUTE :
			Status = 
				GetAudioMonitorMute( pMixerFunction->Channel.wChannel,
										   pMixerFunction->Data.Monitor.wBusOut,
										   pMixerFunction->Data.Monitor.Data.bMuteOn );
			/*
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_MONITOR_MUTE  Status %ld\n", Status) );
			*/
			break;

		case MXF_SET_MONITOR_MUTE :
			Status = 
				SetAudioMonitorMute( pMixerFunction->Channel.wChannel,
										   pMixerFunction->Data.Monitor.wBusOut,
										   pMixerFunction->Data.Monitor.Data.bMuteOn );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_MONITOR_MUTE  Status %ld\n", Status) );
			break;

		case MXF_GET_MONITOR_PAN :
			Status = 
				GetAudioMonitorPan( pMixerFunction->Channel.wChannel,
										  pMixerFunction->Data.Monitor.wBusOut,
										  pMixerFunction->Data.Monitor.Data.iPan);
			/*

			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_MONITOR_PAN  Status %ld\n", Status) );
			*/									 
									 
			break;
		case MXF_SET_MONITOR_PAN :
			Status = 
				SetAudioMonitorPan( pMixerFunction->Channel.wChannel,
										  pMixerFunction->Data.Monitor.wBusOut,
										  pMixerFunction->Data.Monitor.Data.iPan );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_MONITOR_PAN  Status %ld\n", Status) );
			break;

		case MXF_GET_FLAGS :
			pMixerFunction->Data.wFlags = GetFlags();
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_FLAGS 0x%x  Status %ld\n",
									 pMixerFunction->Data.wFlags,
									 Status) );
			break;
		case MXF_SET_FLAGS :
			SetFlags( pMixerFunction->Data.wFlags );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_FLAGS 0x%x  Status %ld\n",
									 pMixerFunction->Data.wFlags,
									 Status) );
			break;
		case MXF_CLEAR_FLAGS :
			ClearFlags( pMixerFunction->Data.wFlags );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_CLEAR_FLAGS 0x%x  Status %ld\n",
									 pMixerFunction->Data.wFlags,
									 Status) );
			break;

		case MXF_GET_SAMPLERATE_LOCK :
			GetAudioLockedSampleRate( pMixerFunction->Data.dwLockedSampleRate );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_SAMPLERATE_LOCK 0x%lx  Status %ld\n",
									 pMixerFunction->Data.dwLockedSampleRate,
									 Status) );
			break;

		case MXF_SET_SAMPLERATE_LOCK :
			SetAudioLockedSampleRate( pMixerFunction->Data.dwLockedSampleRate );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_SAMPLERATE_LOCK 0x%lx  Status %ld\n",
									 pMixerFunction->Data.dwLockedSampleRate,
									 Status) );
			break;

		case MXF_GET_SAMPLERATE :
		
			GetAudioSampleRate( &pMixerFunction->Data.dwSampleRate );

			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_SAMPLERATE 0x%lx  Status %ld\n",
									 pMixerFunction->Data.dwSampleRate,
									 Status) );
			break;

#ifdef MIDI_SUPPORT			

		case MXF_GET_MIDI_IN_ACTIVITY :
			pMixerFunction->Data.bMidiActive = m_MidiIn.IsActive();

			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_MIDI_IN_ACTIVITY %s "
									 "Status %ld\n",
									 ( pMixerFunction->Data.bMidiActive ) 
										? "ACTIVE" : "INACTIVE",
									 Status) );
			break;


		case MXF_GET_MIDI_OUT_ACTIVITY :
			pMixerFunction->Data.bMidiActive =
				GetDspCommObject()->IsMidiOutActive();

			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_MIDI_OUT_ACTIVITY %s "
									 "Status %ld\n",
									 ( pMixerFunction->Data.bMidiActive ) 
										? "ACTIVE" : "INACTIVE",
									 Status) );
			break;

#endif // MIDI_SUPPORT


		case MXF_GET_DIGITAL_MODE :
			Status = ECHOSTATUS_OK;
			pMixerFunction->Data.iDigMode = GetDigitalMode();
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_DIGITAL_MODE %s "
									 "Status %ld\n",
									 ( DIGITAL_MODE_SPDIF_RCA == 
											pMixerFunction->Data.iDigMode )
										? "S/PDIF RCA" 
										: ( DIGITAL_MODE_SPDIF_OPTICAL == 
											pMixerFunction->Data.iDigMode )
												? "S/PDIF Optical" : "ADAT",
									 Status) );
			break;


		case MXF_SET_DIGITAL_MODE :
			Status = SetDigitalMode( (BYTE) pMixerFunction->Data.iDigMode );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_DIGITAL_MODE %s "
									 "Status %ld\n",
									 ( DIGITAL_MODE_SPDIF_RCA == 
											pMixerFunction->Data.iDigMode )
										? "S/PDIF RCA" 
										: ( DIGITAL_MODE_SPDIF_OPTICAL == 
											pMixerFunction->Data.iDigMode )
												? "S/PDIF Optical" : "ADAT",
									 Status) );
			break;


		case MXF_GET_PAN :
			Status = GetAudioPan( pMixerFunction);
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_PAN  Status %ld\n", Status) );
			break;

		case MXF_SET_PAN :
			Status = SetAudioPan( pMixerFunction);
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_PAN  Status %ld\n", Status) );
			break;
			
#ifdef DIGITAL_INPUT_AUTO_MUTE_SUPPORT

		case MXF_GET_DIG_IN_AUTO_MUTE	:
			Status = GetDigitalInAutoMute( pMixerFunction );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_GET_DIG_IN_AUTO_MUTE  Status %ld\n", Status) );
			break;
			

		case MXF_SET_DIG_IN_AUTO_MUTE	:
			Status = SetDigitalInAutoMute( pMixerFunction );
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "MXF_SET_DIG_IN_AUTO_MUTE  Status %ld\n", Status) );
			break;

#endif // DIGITAL_INPUT_AUTO_MUTE_SUPPORT


		default :
			iRtnDataSz = 0;
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerFunction: "
									 "Function %ld not supported\n",
									 pMixerFunction->iFunction) );
			return ECHOSTATUS_NOT_SUPPORTED;
	}

	pMixerFunction->RtnStatus = Status;
	iRtnDataSz = sizeof( MIXER_FUNCTION );

	return Status;

}	// ECHOSTATUS CEchoGals::ProcessMixerFunction




//===========================================================================
//
// Process multiple mixer functions
//
//===========================================================================

ECHOSTATUS CEchoGals::ProcessMixerMultiFunction
(
	PMIXER_MULTI_FUNCTION	pMixerFunctions,	// Request from mixer
	INT32 &							iRtnDataSz			// # bytes returned (if any)
)
{
	ECHOSTATUS			Status = ECHOSTATUS_NOT_SUPPORTED;
	PMIXER_FUNCTION	pMixerFunction;
	INT32					iRtn, nCard, i;
	
	if (m_fMixerDisabled)
		return ECHOSTATUS_MIXER_DISABLED;

	iRtnDataSz = sizeof( MIXER_MULTI_FUNCTION ) - sizeof( MIXER_FUNCTION );
	pMixerFunction = &pMixerFunctions->MixerFunction[ 0 ];
	nCard = pMixerFunction->Channel.wCardId;
	for ( i = 0; i < pMixerFunctions->iCount; i++ )
	{
		pMixerFunction = &pMixerFunctions->MixerFunction[ i ];
		if ( nCard != pMixerFunction->Channel.wCardId )
		{
			ECHO_DEBUGPRINTF( ("CEchoGals::ProcessMixerMultiFunction: "
									 "All functions MUST be for the same card "
									 "exp %ld act %d!\n",
									 nCard,
									 pMixerFunction->Channel.wCardId) );
			return ECHOSTATUS_NOT_SUPPORTED;
		}

		Status = ProcessMixerFunction(pMixerFunction,iRtn);
		iRtnDataSz += iRtn;
	}

	return Status;

}	// ECHOSTATUS CEchoGals::ProcessMixerMultiFunction




//===========================================================================
//
// Typically, if you are writing a console, you will be polling the driver
// to get the current peak and VU meters, clock detect bits, and 
// control changes.  GetPolledStuff will fill out an ECHO_POLLED_STUFF
// structure with all of those things.
//
//===========================================================================

ECHOSTATUS CEchoGals::GetPolledStuff(ECHO_POLLED_STUFF *pPolledStuff)
{
	CDspCommObject *pDCO = GetDspCommObject();
	
	if (m_fMixerDisabled)
		return ECHOSTATUS_MIXER_DISABLED;
	
	if (NULL == pDCO)
		return ECHOSTATUS_DSP_DEAD;
		
	//
	// Match the client to the cookie
	//
	ECHO_MIXER_CLIENT *pClient = GetMixerClient(pPolledStuff->dwCookie);
	
	if (NULL == pClient)
		return ECHOSTATUS_BAD_COOKIE;		
	
	//
	// Fill out the struct
	//
	pDCO->GetAudioMeters(&(pPolledStuff->Meters));
	GetInputClockDetect(pPolledStuff->dwClockDetectBits);
	pPolledStuff->dwNumPendingNotifies = pClient->dwCount;
	
	return ECHOSTATUS_OK;
	
}	// GetPolledStuff


//===========================================================================
//
//	Get the pan setting for an output pipe (virtual outputs only)
//
//===========================================================================

ECHOSTATUS CEchoGals::GetAudioPan
(
	PMIXER_FUNCTION	pMF
)
{
	WORD 			wPipe;
	WORD 			wBus;
	ECHOSTATUS 	Status;
	
	if ( 	(pMF->Channel.dwType != ECHO_PIPE_OUT) ||
			( !HasVmixer() ) )
		return ECHOSTATUS_INVALID_CHANNEL;
		
	wPipe = pMF->Channel.wChannel;
	wBus = pMF->Data.PipeOut.wBusOut;
	Status = m_PipeOutCtrl.GetPan(wPipe,
											wBus,
											pMF->Data.PipeOut.Data.iPan);

	return Status;

}	// ECHOSTATUS CEchoGals::GetAudioPan


//===========================================================================
//
//	Set the pan for an output pipe (virtual outputs only)
//
//===========================================================================

ECHOSTATUS CEchoGals::SetAudioPan
(
	PMIXER_FUNCTION	pMF
)
{
	WORD 			wPipe;
	WORD 			wBus;
	ECHOSTATUS 	Status;
	
	if ( 	(pMF->Channel.dwType != ECHO_PIPE_OUT) ||
			( !HasVmixer() ) )
		return ECHOSTATUS_INVALID_CHANNEL;
		
	wPipe = pMF->Channel.wChannel;
	wBus = pMF->Data.PipeOut.wBusOut;
	Status = m_PipeOutCtrl.SetPan(wPipe,
											wBus,
											pMF->Data.PipeOut.Data.iPan);

	return Status;
	
}	// ECHOSTATUS CEchoGals::SetAudioPan




/****************************************************************************

	CEchoGals clock control
	
	The input clock is the sync source - is the audio for this card running
	off of the internal clock, synced to word clock, etc.
	
	Output clock is only supported on Layla20 - Layla20 can transmit either
	word or super clock.

 ****************************************************************************/

//===========================================================================
//
// Get input and output clocks - just return the stored value
//
//===========================================================================

ECHOSTATUS CEchoGals::GetInputClock(WORD &wClock)
{
	if ( NULL == GetDspCommObject() )
		return ECHOSTATUS_DSP_DEAD;

	wClock = GetDspCommObject()->GetInputClock();
	
	return ECHOSTATUS_OK;
}	


ECHOSTATUS CEchoGals::GetOutputClock(WORD &wClock)
{
	if ( NULL == GetDspCommObject() )
		return ECHOSTATUS_DSP_DEAD;

	wClock = GetDspCommObject()->GetOutputClock();
	
	return ECHOSTATUS_OK;
}	


//===========================================================================
//
// Set input and output clocks - pass it down to the comm page and
// store the control change.
//
//===========================================================================

ECHOSTATUS CEchoGals::SetInputClock(WORD wClock)
{
	ECHOSTATUS Status;

	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
		return ECHOSTATUS_DSP_DEAD;

	ECHO_DEBUGPRINTF( ("CEchoGals::SetInputClock: ") );
	
	Status = GetDspCommObject()->SetInputClock( wClock );

	if (ECHOSTATUS_OK == Status)
	{
		MixerControlChanged( ECHO_NO_CHANNEL_TYPE,
									MXN_INPUT_CLOCK);
	}
	return Status;

}	// SetInputClock


ECHOSTATUS CEchoGals::SetOutputClock(WORD wClock)
{
	ECHOSTATUS Status;

	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
		return ECHOSTATUS_DSP_DEAD;

	ECHO_DEBUGPRINTF( ("CEchoGals::SetOutputClock: ") );
	
	Status = GetDspCommObject()->SetOutputClock( wClock );

	if (ECHOSTATUS_OK == Status)
	{
		MixerControlChanged( ECHO_NO_CHANNEL_TYPE,
									MXN_OUTPUT_CLOCK);
	}
	return Status;

}	


//===========================================================================
//
// Get the currently detected clock bits - default method.  Overridden by 
// derived card classes.
//
//===========================================================================

ECHOSTATUS CEchoGals::GetInputClockDetect(DWORD &dwClockDetectBits)
{
	dwClockDetectBits = ECHO_CLOCK_INTERNAL;

	return ECHOSTATUS_OK;
}	


//===========================================================================
//
// Set the locked sample rate
//
//===========================================================================

ECHOSTATUS CEchoGals::SetAudioLockedSampleRate
(
	DWORD		dwSampleRate
)
{
	ECHOSTATUS	Status;
							 
	Status = QueryAudioSampleRate( dwSampleRate );
	if ( ECHOSTATUS_OK != Status )
		return Status;

	if (0 != (ECHOGALS_FLAG_SAMPLE_RATE_LOCKED & GetFlags()))
	{
		GetDspCommObject()->SetSampleRate( dwSampleRate );
	}

	m_dwLockedSampleRate = dwSampleRate;

	return ECHOSTATUS_OK;

}	// ECHOSTATUS CEchoGals::SetAudioLockedSampleRate


//===========================================================================
//
// Get the locked sample rate
//
//===========================================================================

ECHOSTATUS CEchoGals::GetAudioLockedSampleRate
(
	DWORD	   &dwSampleRate
)
{
	dwSampleRate = m_dwLockedSampleRate;
		
	return ECHOSTATUS_OK;
	
}	// ECHOSTATUS CEchoGals::GetAudioLockedSampleRate



#ifdef DIGITAL_INPUT_AUTO_MUTE_SUPPORT

//===========================================================================
//
// Get the digital input auto mute flag from the comm page
//
//===========================================================================

ECHOSTATUS CEchoGals::GetDigitalInAutoMute(PMIXER_FUNCTION pMixerFunction)
{
	BOOL fAutoMute;
	
	if (0 == (m_wFlags & ECHOGALS_ROFLAG_DIGITAL_IN_AUTOMUTE))
	{
		pMixerFunction->Data.fDigitalInAutoMute = FALSE;
		return ECHOSTATUS_NOT_SUPPORTED;
	}

	GetDspCommObject()->GetDigitalInputAutoMute(	fAutoMute );
	pMixerFunction->Data.fDigitalInAutoMute = fAutoMute;
	
	return ECHOSTATUS_OK;		
	
} // GetDigitalInAutoMute


//===========================================================================
//
// Set the digital input auto mute flag
//
//===========================================================================

ECHOSTATUS CEchoGals::SetDigitalInAutoMute(PMIXER_FUNCTION pMixerFunction)
{
	BOOL fAutoMute;
	
	if (0 == (m_wFlags & ECHOGALS_ROFLAG_DIGITAL_IN_AUTOMUTE))
		return ECHOSTATUS_NOT_SUPPORTED;
	
	fAutoMute = pMixerFunction->Data.fDigitalInAutoMute;
	GetDspCommObject()->SetDigitalInputAutoMute( fAutoMute );	
	
	return ECHOSTATUS_OK;		
	
} // SetDigitalInAutoMute

#endif // DIGITAL_INPUT_AUTO_MUTE_SUPPORT


//===========================================================================
//
// Get the gain for an output bus, input bus, or output pipe.
//
// Gain levels are in units of dB * 256.
//
//===========================================================================

ECHOSTATUS CEchoGals::GetAudioLineLevel
(
	PMIXER_FUNCTION	pMF
)
{
	WORD 			wPipe;
	WORD 			wBus;
	ECHOSTATUS 	Status;

	switch (pMF->Channel.dwType)
	{
		case ECHO_BUS_OUT :
			
			wBus = pMF->Channel.wChannel;
			
			if (wBus < GetNumBussesOut())
			{
				pMF->Data.iLevel = m_BusOutLineLevels[wBus].GetGain();				
				Status = ECHOSTATUS_OK;
			}
			else
			{
				Status = ECHOSTATUS_INVALID_CHANNEL;
			}

			break;
 
		case ECHO_BUS_IN :

			wBus = pMF->Channel.wChannel;
			if (wBus < GetNumBussesIn())
			{
				pMF->Data.iLevel = m_BusInLineLevels[wBus].GetGain();				
				Status = ECHOSTATUS_OK;
			}
			else
			{
				Status = ECHOSTATUS_INVALID_CHANNEL;
			}
			break;
		
		case ECHO_PIPE_OUT :
		
			wPipe = pMF->Channel.wChannel;
			wBus = pMF->Data.PipeOut.wBusOut;
 			Status = m_PipeOutCtrl.GetGain(	wPipe,
														wBus,
														pMF->Data.PipeOut.Data.iLevel);
			break;

		default:
			Status = ECHOSTATUS_INVALID_PARAM;
			break;
	}

	return Status;

}	// ECHOSTATUS CEchoGals::GetAudioLineLevel


//===========================================================================
//
// Utility function to check that a setting is within the correct range.
//
//===========================================================================

ECHOSTATUS CheckSetting(INT32 iValue,INT32 iMin,INT32 iMax)
{
	if ( (iValue > iMax) || (iValue < iMin))
		return ECHOSTATUS_INVALID_PARAM;
	
	return ECHOSTATUS_OK;	
		
}	// CheckSetting


//===========================================================================
//
// Set the gain for an output bus, input bus, or output pipe.
//
// Gain levels are in units of dB * 256.
//
//===========================================================================

ECHOSTATUS CEchoGals::SetAudioLineLevel
(
	PMIXER_FUNCTION	pMF
)
{
	WORD 			wPipe;
	WORD 			wBus;
	ECHOSTATUS 	Status;
	INT32 			iLevel;

	switch (pMF->Channel.dwType)
	{
		case ECHO_BUS_OUT :

			wBus = pMF->Channel.wChannel;
			iLevel = pMF->Data.iLevel;
			
			Status = CheckSetting(iLevel,ECHOGAIN_MINOUT,ECHOGAIN_MAXOUT);
			if (ECHOSTATUS_OK != Status)
				break;
			
			Status = m_BusOutLineLevels[wBus].SetGain(iLevel);
			break;
			
		case ECHO_BUS_IN :
		
			wBus = pMF->Channel.wChannel;
			iLevel = pMF->Data.iLevel;

			Status = CheckSetting(iLevel,ECHOGAIN_MININP,ECHOGAIN_MAXINP);
			if (ECHOSTATUS_OK != Status)
				break;

			Status = m_BusInLineLevels[wBus].SetGain(iLevel);
			break;
			
		case ECHO_PIPE_OUT :

			wPipe = pMF->Channel.wChannel;
			wBus = pMF->Data.PipeOut.wBusOut;
			iLevel = pMF->Data.PipeOut.Data.iLevel;

			Status = CheckSetting(iLevel,ECHOGAIN_MINOUT,ECHOGAIN_MAXOUT);
			if (ECHOSTATUS_OK != Status)
				break;

 			Status = m_PipeOutCtrl.SetGain(	wPipe,
														wBus,
														iLevel);
			break;
			
		default:
			Status = ECHOSTATUS_INVALID_PARAM;
			break;
	}

	return Status;
	
}	// ECHOSTATUS CEchoGals::SetAudioLineLevel


//===========================================================================
//
// Get the nominal level for an output or input bus.  The nominal level is
// also referred to as the +4/-10 switch.
//
//===========================================================================

ECHOSTATUS CEchoGals::GetAudioNominal
(
	PMIXER_FUNCTION	pMF
)
{
	BYTE					byNominal;
	ECHOSTATUS			Status;
	CDspCommObject *	pDspCommObj = GetDspCommObject();
	WORD					wCh;

	if ( NULL == pDspCommObj || pDspCommObj->IsBoardBad() )
		return ECHOSTATUS_DSP_DEAD;
		
	switch (pMF->Channel.dwType)
	{
		case ECHO_BUS_OUT :
			wCh = pMF->Channel.wChannel;
			break;
			
		case ECHO_BUS_IN :
			wCh = pMF->Channel.wChannel + GetNumBussesOut();
			break;
		
		default :
			return ECHOSTATUS_INVALID_CHANNEL;
	}
		
	Status = pDspCommObj->GetNominalLevel( wCh, &byNominal );

	if ( ECHOSTATUS_OK != Status )
		return Status;

	pMF->Data.iNominal = ( byNominal ) ? -10 : 4;

	return ECHOSTATUS_OK;
}	// ECHOSTATUS CEchoGals::GetAudioNominal


//===========================================================================
//
// Set the nominal level for an output or input bus.  The nominal level is
// also referred to as the +4/-10 switch.
//
//===========================================================================

ECHOSTATUS CEchoGals::SetAudioNominal
(
	PMIXER_FUNCTION	pMF
)
{
	ECHOSTATUS	Status;
	WORD			wCh;
	int			iNominal;

	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
		return ECHOSTATUS_DSP_DEAD;
	
	switch (pMF->Channel.dwType)
	{
		case ECHO_BUS_OUT :
			wCh = pMF->Channel.wChannel;
			break;
			
		case ECHO_BUS_IN :
			wCh = pMF->Channel.wChannel + GetNumBussesOut();
			break;
		
		default :
			return ECHOSTATUS_INVALID_CHANNEL;
	}

	iNominal = pMF->Data.iNominal;
	
	if ((iNominal!= -10) && (iNominal != 4))
		return ECHOSTATUS_INVALID_PARAM;
	
	Status =
		GetDspCommObject()->SetNominalLevel( wCh,
														( iNominal == -10 ) );

	if ( ECHOSTATUS_OK != Status )
		return Status;

	Status = MixerControlChanged( (WORD) pMF->Channel.dwType,
											MXN_NOMINAL,
											pMF->Channel.wChannel);
	return Status;

}	// ECHOSTATUS CEchoGals::SetAudioNominal


//===========================================================================
//
// Set the mute for an output bus, input bus, or output pipe.
//
//===========================================================================

ECHOSTATUS CEchoGals::SetAudioMute
(
		PMIXER_FUNCTION	pMF
)
{
	WORD 			wPipe;
	WORD 			wBus;
	ECHOSTATUS 	Status;
	BOOL			bMute;
	
	switch (pMF->Channel.dwType)
	{
		case ECHO_BUS_OUT :

			wBus = pMF->Channel.wChannel;
			bMute = pMF->Data.bMuteOn;
			Status = m_BusOutLineLevels[wBus].SetMute(bMute);
			break;
			
		case ECHO_BUS_IN :
	
			wBus = pMF->Channel.wChannel;
			bMute = pMF->Data.bMuteOn;
			Status = m_BusInLineLevels[wBus].SetMute(bMute);
			break;
			
		case ECHO_PIPE_OUT :

			wPipe = pMF->Channel.wChannel;
			wBus = pMF->Data.PipeOut.wBusOut;
			bMute = pMF->Data.PipeOut.Data.bMuteOn;
 			Status = m_PipeOutCtrl.SetMute(	wPipe,
														wBus,
														bMute);
			break;
			
		default:
			Status = ECHOSTATUS_INVALID_PARAM;
			break;
	}

	return Status;
}	// ECHOSTATUS CEchoGals::SetAudioMute


//===========================================================================
//
// Get the mute for an output bus, input bus, or output pipe.
//
//===========================================================================

ECHOSTATUS CEchoGals::GetAudioMute
(
		PMIXER_FUNCTION	pMF
)
{
	WORD 			wPipe;
	WORD 			wBus;
	ECHOSTATUS 	Status;

	switch (pMF->Channel.dwType)
	{
		case ECHO_BUS_OUT :
			
			wBus = pMF->Channel.wChannel;
			
			if (wBus < GetNumBussesOut())
			{
				pMF->Data.bMuteOn = m_BusOutLineLevels[wBus].IsMuteOn();
				Status = ECHOSTATUS_OK;
			}
			else
			{
				Status = ECHOSTATUS_INVALID_CHANNEL;
			}

			break;
 
		case ECHO_BUS_IN :

			wBus = pMF->Channel.wChannel;
			
			if (wBus < GetNumBussesIn())
			{
				pMF->Data.bMuteOn = m_BusInLineLevels[wBus].IsMuteOn();
				Status = ECHOSTATUS_OK;
			}
			else
			{
				Status = ECHOSTATUS_INVALID_CHANNEL;
			}
			break;
		
		case ECHO_PIPE_OUT :
		
			wPipe = pMF->Channel.wChannel;
			wBus = pMF->Data.PipeOut.wBusOut;
 			Status = m_PipeOutCtrl.GetMute(	wPipe,
														wBus,
														pMF->Data.PipeOut.Data.bMuteOn);
			break;

		default:
			Status = ECHOSTATUS_INVALID_PARAM;
			break;
	}

	return Status;

}	// ECHOSTATUS CEchoGals::GetAudioMute


//===========================================================================
//
// Get the monitor gain for a single input bus mixed to a single output bus.
//
//===========================================================================

ECHOSTATUS CEchoGals::GetAudioMonitor
(
	WORD	wBusIn,
	WORD	wBusOut,
	INT32 &	iGain
)
{
	if ( wBusIn  >= GetNumBussesIn() ||
		  wBusOut >= GetNumBussesOut() )
	{
		return ECHOSTATUS_INVALID_INDEX;
	}
	
	//
	// Get the monitor value
	//
	m_MonitorCtrl.GetGain(wBusIn,wBusOut,iGain);
	
	return ECHOSTATUS_OK;

}	// ECHOSTATUS CEchoGals::GetAudioMonitor


//===========================================================================
//
// Set the monitor gain for a single input bus mixed to a single output bus.
//
//===========================================================================

ECHOSTATUS CEchoGals::SetAudioMonitor
(
	WORD	wBusIn,
	WORD	wBusOut,
	INT32	iGain
)
{
	ECHOSTATUS	Status;

	if ( wBusIn  >= GetNumBussesIn() ||
		  wBusOut >= GetNumBussesOut() )
	{
		return ECHOSTATUS_INVALID_INDEX;
	}
	
	Status = CheckSetting(iGain,ECHOGAIN_MINOUT,ECHOGAIN_MAXOUT);
	if (ECHOSTATUS_OK == Status)
	{
		//
		// Set the monitor gain
		// 
		m_MonitorCtrl.SetGain(wBusIn,wBusOut,iGain);
	}

	return Status;
	
}	// ECHOSTATUS CEchoGals::SetAudioMonitor


//===========================================================================
//
//	Helper functions for doing log conversions on pan values
//
// The parameter iNum is a fixed point 32 bit number in 16.16 format;
// that is, 16 bits of integer and 16 bits of decimal
// To convert a float number to fixed point, simply multiply by 2^16 and round
//
// Valid range for iNum is from +1.0 (0x10000) to 0.
//
//===========================================================================

#define FIXED_BASE		16							// 16 bits of fraction
#define FIXED_ONE_HALF	((int) 0x00008000)	// 0.5 in 16.16 format
#define COEFF_A2			((int) 0xffffa9ac)	// -.3372223
#define COEFF_A1			((int) 0x0000ff8a)	//  .9981958
#define COEFF_A0			((int) 0xffff5661)	// -.6626105

#define DB_CONVERT		0x60546		// 6.02... in 16.16

// Note use of double precision here to prevent overflow
static int FixedMult( int iNum1, int iNum2 )
{
	LONGLONG llNum;

	llNum = (LONGLONG) iNum1 * (LONGLONG) iNum2;

	return (int) (llNum >> FIXED_BASE);
}	// int FixedMult( int iNum1, int iNum2 )


static int log2( int iNum )
{
	int iNumShifts;
	int iTemp;

	// log2 is undefined for zero, so return -infinity (or close enough)
	if ( 0 == iNum )
		return ECHOGAIN_MUTED;

	// Step 1 - Normalize and save the number of shifts
	// Keep shifting iNum up until iNum > 0.5
	iNumShifts = 0;
	while ( iNum < FIXED_ONE_HALF )
	{
		iNumShifts++;
		iNum <<= 1;
	}

	// Step 2 - Calculate LOG2 by polynomial approximation.  8 bit accuracy.
	//
	// LOG2(x) = 4.0* (-.3372223 x*x + .9981958 x - .6626105)
	//                           a2             a1            a0
	// where  0.5 <= x < 1.0
	//

	// Compute polynomial sum
	iTemp = FixedMult( iNum, iNum );				// iTemp now has iNum squared
	iTemp = FixedMult( iTemp, COEFF_A2 );
	iTemp += FixedMult( iNum, COEFF_A1 );
	iTemp += COEFF_A0;

	// Multiply by four
	iTemp <<= 2;

	// Account for the normalize shifts
	iTemp -= ( iNumShifts << FIXED_BASE );

	return( iTemp );
}	// int log2( int iNum )


//
//	Convert pan value to Db X 256
//	Pan value is 0 - MAX_MIXER_PAN
//
INT32 PanToDb( INT32 iPan )
{
	if ( iPan >= ( MAX_MIXER_PAN - 1 ) )
		return( 0 );
	if ( iPan <= 1 )
		return( ECHOGAIN_MUTED );
	//
	//	Convert pan to 16.16
	//
	iPan = ( iPan << 16 ) / MAX_MIXER_PAN;
	//
	//	Take the log
	//
	iPan = log2( iPan );
	//
	// To convert to decibels*256, just multiply by the conversion factor
	//
	iPan = FixedMult( iPan << 8, DB_CONVERT );
	//
	// To round, add one half and then mask off the fractional bits
	//
	iPan = ( iPan + FIXED_ONE_HALF ) >> FIXED_BASE;
	return( iPan );
}	// INT32 PanToDb( INT32 iPan )


//===========================================================================
//
// Set the monitor pan
//
//	For this to work effectively, both the input and output channels must
//	both either be odd or even.  Thus even channel numbers are for the
//	left channel and odd channel numbers are for the right channel.
//	Pan values will be computed for both channels.
//
// iPan ranges from 0 (hard left) to MAX_MIXER_PAN (hard right)
//
//===========================================================================

ECHOSTATUS CEchoGals::SetAudioMonitorPan
(
	WORD	wBusIn,
	WORD	wBusOut,
	INT32	iPan						// New pan
)
{
	ECHOSTATUS Status;

	if ( wBusIn  >= GetNumBussesIn() ||
		  wBusOut >= GetNumBussesOut() )
	{
		return ECHOSTATUS_INVALID_INDEX;
	}
	
	Status = CheckSetting(iPan,0,MAX_MIXER_PAN);
	if (ECHOSTATUS_OK == Status)
	{
		//
		// Set the pan
		// 
		m_MonitorCtrl.SetPan(wBusIn,wBusOut,iPan);
	}

	return Status;
		
}	// ECHOSTATUS CEchoGals::SetAudioMonitorPan


//===========================================================================
//
// Get the monitor pan
//
//===========================================================================

ECHOSTATUS CEchoGals::GetAudioMonitorPan
(
	WORD	wBusIn,
	WORD	wBusOut,
	INT32 &	iPan						// Returns current pan (0 - MAX_MIXER_PAN)
)
{
	if ( wBusIn  >= GetNumBussesIn() ||
		  wBusOut >= GetNumBussesOut() )
	{
		return ECHOSTATUS_INVALID_INDEX;
	}
	
	//
	// Get the pan
	// 
	m_MonitorCtrl.GetPan(wBusIn,wBusOut,iPan);
	
	return ECHOSTATUS_OK;

}	// ECHOSTATUS CEchoGals::GetAudioMonitorPan


//===========================================================================
//
// Set the monitor mute
//
//===========================================================================

ECHOSTATUS CEchoGals::SetAudioMonitorMute
(
	WORD	wBusIn,
	WORD	wBusOut,
	BOOL	bMute						// New state
)
{
	if ( wBusIn  >= GetNumBussesIn() ||
		  wBusOut >= GetNumBussesOut() )
	{
		return ECHOSTATUS_INVALID_INDEX;
	}
	
	//
	// Set the mute
	// 
	m_MonitorCtrl.SetMute(wBusIn,wBusOut,bMute);
		
	return ECHOSTATUS_OK;
	
}	// ECHOSTATUS CEchoGals::SetAudioMonitorMute


//===========================================================================
//
// Get the monitor mute
//
//===========================================================================

ECHOSTATUS CEchoGals::GetAudioMonitorMute
(
	WORD	wBusIn,
	WORD	wBusOut,
	BOOL 	&bMute	  				// Returns current state
)
{
	if ( wBusIn  >= GetNumBussesIn() ||
		  wBusOut >= GetNumBussesOut() )
	{
		return ECHOSTATUS_INVALID_INDEX;
	}
	
	//
	// Get the mute
	// 
	m_MonitorCtrl.GetMute(wBusIn,wBusOut,bMute);
	
	return ECHOSTATUS_OK;
	
}	// ECHOSTATUS CEchoGals::GetAudioMonitorMute


//===========================================================================
//
// Set the S/PDIF output format to professional or consumer
//
//===========================================================================

void CEchoGals::SetProfessionalSpdif( BOOL bNewStatus )
{
	ECHO_DEBUGPRINTF(("CEchoGals::SetProfessionalSpdif %d\n",bNewStatus));
	
	if ( NULL != GetDspCommObject() )
	{
		GetDspCommObject()->SetProfessionalSpdif( bNewStatus );
		MixerControlChanged( ECHO_NO_CHANNEL_TYPE,
									MXN_SPDIF );
	}
}	// void CEchoGals::SetProfessionalSpdif( BOOL bNewStatus )


//===========================================================================
//
// Set driver flags
//
//	See ECHOGALS_FLAG_??? definitions in EchoGalsXface.h
//
//===========================================================================

ECHOSTATUS CEchoGals::SetFlags
(
	WORD	wFlags
)
{
	//
	// Mask off the read-only flags so they don't change
	//
	wFlags &= ECHOGALS_FLAG_WRITABLE_MASK;

	//
	// Set the flags & mark the flags as changed
	//
	m_wFlags |= wFlags;

	MixerControlChanged(	ECHO_NO_CHANNEL_TYPE,
								MXN_FLAGS );

	return ECHOSTATUS_OK;

}	// ECHOSTATUS CEchoGals::SetFlags


//===========================================================================
//
// Clear driver flags
//
//	See ECHOGALS_FLAG_??? definitions in EchoGalsXface.h
//
//===========================================================================

ECHOSTATUS CEchoGals::ClearFlags
(
	WORD	wFlags
)
{
	//
	// Mask off the read-only flags so they don't change
	//
	wFlags &= ECHOGALS_FLAG_WRITABLE_MASK;

	//
	// Clear the flags & mark the flags as changed
	//
	m_wFlags &= ~wFlags;

	MixerControlChanged(	ECHO_NO_CHANNEL_TYPE,
								MXN_FLAGS );

	return ECHOSTATUS_OK;
	
}	// ECHOSTATUS CEchoGals::ClearFlags


//===========================================================================
//
// Set the digital mode - currently for Gina24, Layla24, and Mona
//
//===========================================================================

ECHOSTATUS CEchoGals::SetDigitalMode
(
	BYTE byDigitalMode
)
{
	ECHOSTATUS	Status;
	BYTE			byPreviousDigitalMode;

	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
		return ECHOSTATUS_DSP_DEAD;

	if ( 0 == GetDspCommObject()->GetDigitalModes() )
		return ECHOSTATUS_DIGITAL_MODE_NOT_SUPPORTED;

	if ( !m_cmAudioOpen.IsEmpty() )
	{
		ECHO_DEBUGPRINTF( ( "CEchoGals::SetDigitalMode()  All audio channels "
								  "must be closed before changing the digital "
								  "mode\n" ) );
		return ECHOSTATUS_DIGITAL_MODE_NOT_SUPPORTED;
	}
	byPreviousDigitalMode = GetDspCommObject()->GetDigitalMode();
	Status = GetDspCommObject()->SetDigitalMode( byDigitalMode );
	MixerControlChanged( ECHO_NO_CHANNEL_TYPE,
								MXN_DIGITAL_MODE );
	//
	//	If we successfully changed the digital mode from or to ADAT, then 
	//	make sure all output, input and monitor levels are updated by the
	//	DSP comm object.
	//
	if ( ECHOSTATUS_OK == Status &&
		  byPreviousDigitalMode != byDigitalMode &&
		  ( DIGITAL_MODE_ADAT == byPreviousDigitalMode ||
		    DIGITAL_MODE_ADAT == byDigitalMode ) )
	{
		WORD	i, j,wBus,wPipe;

		for ( i = 0; i < GetNumBussesIn(); i++ )
		{
			for ( j = 0; j < GetNumBussesOut(); j += 2 )
			{
				m_MonitorCtrl.SetGain(i,j,ECHOGAIN_UPDATE,FALSE);
			}
		}

		for ( wBus = 0; wBus < GetNumBussesOut(); wBus++)
		{
			for ( wPipe = 0; wPipe < GetNumPipesOut(); wPipe++)
			{
				m_PipeOutCtrl.SetGain(wPipe,wBus,ECHOGAIN_UPDATE,FALSE);
			}
		}

		for ( i = 0; i < GetNumBussesOut(); i++ )
		{
			m_BusOutLineLevels[ i ].SetGain(ECHOGAIN_UPDATE,FALSE);
		}

		for ( i = 0; i < GetNumBussesIn(); i++ )
		{
			m_BusInLineLevels[ i ].SetGain( ECHOGAIN_UPDATE );
		}
		
		//
		// Now set them all at once, since all the
		// fImmediate parameters were set to FALSE.  Do the output
		// bus _and_ the output pipe in case this is a vmixer card.
		//
		m_BusOutLineLevels[0].SetGain(ECHOGAIN_UPDATE,TRUE);
		m_PipeOutCtrl.SetGain(0,0,ECHOGAIN_UPDATE,TRUE);
	}
	
	//
	// If the card has just been put in ADAT mode, it is possible
	// that the locked sample rate is greater than 48KHz, which is not allowed
	// in ADAT mode.  If this happens, change the locked rate to 48.
	//
	if ( 	(DIGITAL_MODE_ADAT == byDigitalMode) &&
			(m_wFlags & ECHOGALS_FLAG_SAMPLE_RATE_LOCKED) &&
			(m_dwLockedSampleRate > 48000) )
	{
		m_dwLockedSampleRate = 48000;
	}
	
	return Status;
	
}	// ECHOSTATUS CEchoGals::SetDigitalMode( ... )


/*

The output bus gain controls aren't actually implemented in the hardware;
insted they are virtual controls created by the generic code.

The signal sent to an output bus is a mix of the monitors and output pipes
routed to that bus; the output bus gain is therefore implemented by tweaking
each appropriate monitor and output pipe gain.

*/


//===========================================================================
//
// Adjust all the monitor levels for a particular output bus
//
// For efficiency, fImmediate is set to FALSE in the call
// to SetGain; all the monitor and pipe out gains are
// sent to the DSP at once by AdjustPipesOutForBusOut.
//
//===========================================================================

ECHOSTATUS CEchoGals::AdjustMonitorsForBusOut(WORD wBusOut)
{
	WORD wBusIn;
	
	//
	// Round down to the nearest even bus so the pans work right
	//
	wBusOut &= 0xfffe;

	//
	// Poke the monitors
	//
	for (wBusIn = 0; wBusIn < GetNumBussesIn(); wBusIn++)
	{
		m_MonitorCtrl.SetGain(wBusIn,wBusOut,ECHOGAIN_UPDATE,FALSE);
	}
	
	return ECHOSTATUS_OK;
	
}	// AdjustMonitorsForBusOut

	
//===========================================================================
//
// Adjust all the monitor levels for a particular output bus
//
// For efficiency, fImmediate is set to FALSE in the call
// to SetGain; all the monitor and pipe out gains are
// sent to the DSP at once by AdjustPipesOutForBusOut.
//
//===========================================================================

ECHOSTATUS CEchoGals::AdjustPipesOutForBusOut(WORD wBusOut,int iBusOutGain)
{
	ECHO_DEBUGPRINTF(("CEchoGals::AdjustPipesOutForBusOut wBusOut %d  iBusOutGain %d\n",
							wBusOut,
							iBusOutGain));
					
	//
	// Round down to the nearest even bus
	//
	wBusOut &= 0xfffe;							
							
	m_PipeOutCtrl.SetGain(wBusOut,wBusOut,ECHOGAIN_UPDATE,FALSE);
	wBusOut++;
	m_PipeOutCtrl.SetGain(wBusOut,wBusOut,ECHOGAIN_UPDATE,TRUE);	
	
	return ECHOSTATUS_OK;
	
}	// AdjustPipesOutForBusOut


