root/src/add-ons/kernel/drivers/audio/echo/generic/CEchoGals_mixer.cpp
// ****************************************************************************
//
//              CEchoGals_mixer.cpp
//
//              Implementation file for the CEchoGals driver class (mixer functions).
//              Set editor tabs to 3 for your viewing pleasure.
//
// ----------------------------------------------------------------------------
//
// 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"

#undef ECHO_DEBUGPRINTF
#define ECHO_DEBUGPRINTF(x)

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

        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(NUINT &Cookie)
{
        ECHO_MIXER_CLIENT *pTemp;

        if (m_fMixerDisabled)
                return ECHOSTATUS_MIXER_DISABLED;

        //
        // If the cookie is non-zero, then use the specified value
        //
        if (0 != Cookie)
        {
                pTemp = m_pMixerClients;
                while (pTemp != NULL)
                {
                        if (Cookie == pTemp->Cookie)
                        {
                                ECHO_DEBUGPRINTF(("CEchoGals::OpenMixer - cookie 0x%lx already in use\n",
                                                                                Cookie));
                                return ECHOSTATUS_BAD_COOKIE;
                        }
                        
                        pTemp = pTemp->pNext;
                }
        }
        else
        {
                //
                // Make a new cookie
                //
                ULONGLONG ullTime;

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

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

        
        //
        // Store the new cookie and the new mixer client
        //
        pClient->Cookie = Cookie;
        pClient->pNext = m_pMixerClients;
        m_pMixerClients = pClient;
        
        return ECHOSTATUS_OK;
        
}       // OpenMixer


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

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


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

ECHOSTATUS CEchoGals::CloseMixer(NUINT Cookie)
{
        //
        //      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->Cookie == Cookie)
        {
                m_pMixerClients = pTemp->pNext;
                OsFreeNonPaged(pTemp);
                
                return ECHOSTATUS_OK;
        }
                
        //
        // Not the head of the list; search the list
        //
        while (NULL != pTemp->pNext)
        {
                if (Cookie == pTemp->pNext->Cookie)
                {
                        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,
        NUINT   MixerCookie
)
{
        //
        // Match the cookie
        //
        ECHO_MIXER_CLIENT *pClient = GetMixerClient(MixerCookie);
        
        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 ( NULL == GetDspCommObject() )
                        {
                                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

                case MXF_GET_AUDIO_LATENCY :
                        GetAudioLatency( &(pMixerFunction->Data.Latency) );
                        break;
                        
#ifdef PHANTOM_POWER_CONTROL
                
                case MXF_GET_PHANTOM_POWER :
                        GetPhantomPower( &(pMixerFunction->Data.fPhantomPower) );
                        break;
                        
                case MXF_SET_PHANTOM_POWER :
                        SetPhantomPower( pMixerFunction->Data.fPhantomPower );
                        break;
                        
#endif

                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,
        NUINT MixerCookie
)
{
        ECHO_MIXER_CLIENT *pClient;
        CDspCommObject *pDCO = GetDspCommObject();
        
        if (m_fMixerDisabled)
                return ECHOSTATUS_MIXER_DISABLED;
        
        if (NULL == pDCO)
                return ECHOSTATUS_DSP_DEAD;
                
        //
        // Fill out the non-client-specific portions of the struct
        //
        pDCO->GetAudioMeters(&(pPolledStuff->Meters));
        GetInputClockDetect(pPolledStuff->dwClockDetectBits);
        
        //
        // If there is a matching client, fill out the number
        // of notifies
        //
        pClient = GetMixerClient(MixerCookie);
        if (NULL == pClient)
                pPolledStuff->dwNumPendingNotifies = 0;
        else    
                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_dwSampleRate = 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;
        INT32                   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  ((INT32) 0x00008000)    // 0.5 in 16.16 format
#define COEFF_A2                        ((INT32) 0xffffa9ac)    // -.3372223
#define COEFF_A1                        ((INT32) 0x0000ff8a)    //  .9981958
#define COEFF_A0                        ((INT32) 0xffff5661)    // -.6626105

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

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

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

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


static INT32 log2( INT32 iNum )
{
        INT32 iNumShifts;
        INT32 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 );
}       // INT32 log2( INT32 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 ( TRUE == GetDspCommObject()->IsTransportActive() ) 
        {
                ECHO_DEBUGPRINTF( (     "CEchoGals::SetDigitalMode()  Cannot set the digital "
                                                                        "mode while transport is running\n"));
                return ECHOSTATUS_BUSY;
        }
        byPreviousDigitalMode = GetDspCommObject()->GetDigitalMode();
        Status = GetDspCommObject()->SetDigitalMode( byDigitalMode );
        MixerControlChanged( ECHO_NO_CHANNEL_TYPE,
                                                                MXN_DIGITAL_MODE );
        MixerControlChanged( ECHO_NO_CHANNEL_TYPE, 
                                                                MXN_INPUT_CLOCK );

        //
        //      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;
        
        //
        // Poke the monitors
        //
        for (wBusIn = 0; wBusIn < GetNumBussesIn(); wBusIn++)
        {
                m_MonitorCtrl.SetGain(wBusIn,wBusOut,ECHOGAIN_UPDATE,FALSE);
        }
        
        return ECHOSTATUS_OK;
        
}       // AdjustMonitorsForBusOut

        
//===========================================================================
//
// Adjust all the pipe out 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,INT32 iBusOutGain)
{
        ECHO_DEBUGPRINTF(("CEchoGals::AdjustPipesOutForBusOut wBusOut %d  iBusOutGain %ld\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



//===========================================================================
//
// GetAudioLatency - returns the latency for a single pipe
//
//===========================================================================

void CEchoGals::GetAudioLatency(ECHO_AUDIO_LATENCY *pLatency)
{
        DWORD dwLatency;

        if (FALSE == pLatency->wIsInput)
        {
                if (pLatency->wPipe >= GetFirstDigitalBusOut())
                        dwLatency = m_wDigitalOutputLatency;
                else
                        dwLatency = m_wAnalogOutputLatency;     
        }
        else
        {
                if (pLatency->wPipe >= GetFirstDigitalBusIn())
                        dwLatency = m_wDigitalInputLatency;
                else
                        dwLatency = m_wAnalogInputLatency;      
        }
        
        pLatency->dwLatency = dwLatency;

}       // GetAudioLatency