root/src/add-ons/kernel/drivers/audio/echo/generic/CPipeOutCtrl.cpp
// ****************************************************************************
//
//              CPipeOutCtrl.cpp
//
//              Class to control output pipes on cards with or without vmixers.
//
// ----------------------------------------------------------------------------
//
// 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"
#include "CPipeOutCtrl.h"

extern INT32 PanToDb( INT32 iPan );


//*****************************************************************************
//
// Destructor (this class uses the default constructor)
//
//*****************************************************************************

CPipeOutCtrl::~CPipeOutCtrl()
{
        Cleanup();      
}       


//*****************************************************************************
//
// Init
//
//*****************************************************************************

ECHOSTATUS CPipeOutCtrl::Init(CEchoGals *pEG)
{
        DWORD   dwBytes;
        WORD    wPipe,wStereoBus;

        m_Gains = NULL;
        m_Mutes = NULL;
        m_Pans = NULL;
        m_PanDbs = NULL;
        
        //
        // Cache stuff
        //
        m_pEG = pEG;
        m_wNumPipesOut = pEG->GetNumPipesOut();
        m_wNumBussesOut = pEG->GetNumBussesOut();
        m_fHasVmixer = pEG->HasVmixer();
        
        //
        // Allocate the arrays
        //
        if (m_fHasVmixer)
        {
                WORD wNumStereoBusses = m_wNumBussesOut >> 1;
                
                //
                // Allocate arrays for vmixer support
                //
                dwBytes = sizeof(INT8) * m_wNumPipesOut * wNumStereoBusses;
                OsAllocateNonPaged(dwBytes,(void **) &m_Gains);
                if (NULL == m_Gains)
                {
                        return ECHOSTATUS_NO_MEM;
                }

                dwBytes = sizeof(BYTE) * m_wNumPipesOut * wNumStereoBusses;
                OsAllocateNonPaged(dwBytes,(void **) &m_Mutes);
                if (NULL == m_Mutes)
                {
                        Cleanup();
                        return ECHOSTATUS_NO_MEM;
                }
                
                dwBytes = sizeof(WORD) * m_wNumPipesOut * wNumStereoBusses;
                OsAllocateNonPaged(dwBytes,(void **) &m_Pans);
                if (NULL == m_Pans)
                {
                        Cleanup();
                        return ECHOSTATUS_NO_MEM;
                }
                
                dwBytes = sizeof(PAN_DB) * m_wNumPipesOut * wNumStereoBusses;
                OsAllocateNonPaged(dwBytes,(void **) &m_PanDbs);
                if (NULL == m_PanDbs)
                {
                        Cleanup();
                        return ECHOSTATUS_NO_MEM;
                }
                
                //
                // Initialize pans and mutes
                //
                for (wPipe = 0; wPipe < m_wNumPipesOut; wPipe++)
                {
                        for (wStereoBus = 0; wStereoBus < wNumStereoBusses; wStereoBus++)
                        {
                                WORD wIndex;
                                
                                wIndex = GetIndex(wPipe,wStereoBus << 1);

                                //
                                //      Pans
                                // 
                                if (0 == (wPipe & 1))
                                {
                                        //
                                        // Even channel - pan hard left
                                        //
                                        m_Pans[wIndex] = 0;
                                        m_PanDbs[wIndex].iLeft = 0;
                                        m_PanDbs[wIndex].iRight = GENERIC_TO_DSP( ECHOGAIN_MUTED );
                                }
                                else
                                {
                                        //
                                        // Odd channel - pan hard right
                                        //
                                        m_Pans[wIndex] = MAX_MIXER_PAN;
                                        m_PanDbs[wIndex].iLeft = GENERIC_TO_DSP( ECHOGAIN_MUTED );
                                        m_PanDbs[wIndex].iRight = 0;
                                }
                                
                                //
                                // Mutes
                                //
                                if ((wPipe >> 1) == wStereoBus)
                                {
                                        m_Mutes[wIndex] = FALSE;
                                }
                                else
                                {
                                        m_Mutes[wIndex] = TRUE;
                                }
                                
                                //
                                // Set the gain to the DSP; use fImmedate = FALSE here
                                // to make this faster
                                //
                                SetGain(wPipe,wStereoBus << 1,ECHOGAIN_UPDATE,FALSE);
                                
                        }
                }
                        
                //
                // Set the gain one more time with the immediate flag set to
                // make sure the DSP gets the message
                //
                SetGain(0,0,ECHOGAIN_UPDATE,TRUE);
        }
        else
        {
                //
                // Allocate arrays for no vmixer support - don't need pans
                //
                dwBytes = sizeof(INT8) * m_wNumPipesOut;
                OsAllocateNonPaged(dwBytes,(void **) &m_Gains);
                if (NULL == m_Gains)
                {
                        return ECHOSTATUS_NO_MEM;
                }

                dwBytes = sizeof(BYTE) * m_wNumPipesOut;
                OsAllocateNonPaged(dwBytes,(void **) &m_Mutes);
                if (NULL == m_Mutes)
                {
                        OsFreeNonPaged(m_Gains);
                        return ECHOSTATUS_NO_MEM;
                }
                
        }         
        
        return ECHOSTATUS_OK;
        
}       // Init


//*****************************************************************************
//
// Cleanup - free allocated memory
//
//*****************************************************************************

void CPipeOutCtrl::Cleanup()
{
        if (m_Gains)
                OsFreeNonPaged(m_Gains);
                
        if (m_Mutes)
                OsFreeNonPaged(m_Mutes);
                
        if (m_Pans)
                OsFreeNonPaged(m_Pans);
                
        if (m_PanDbs)
                OsFreeNonPaged(m_PanDbs);
        
}       // Cleanup


//*****************************************************************************
//
// Set and get gain
//
// For cards without vmixers, output bus gain is not handled by the DSP.
// Instead, the driver adjusts the output pipe volumes by the output bus gain
// and sends that value to the DSP.
//
// For cards with vmixers, the output bus gain is handled by the DSP, so
// the gain setting does not need to take into account the output bus gain
// stored by the driver.
//
//*****************************************************************************

ECHOSTATUS CPipeOutCtrl::SetGain
(
        WORD    wPipeOut, 
        WORD    wBusOut, 
        INT32 iGain,
        BOOL    fImmediate
)
{
        INT32 iBusOutGain;
        ECHOSTATUS Status;

        if ( NULL == m_pEG)
                return ECHOSTATUS_DSP_DEAD;
                
        if (!m_fHasVmixer && (wPipeOut != wBusOut))
                return ECHOSTATUS_OK;
        
        if ((NULL == m_Gains) || (NULL == m_Mutes))
                return ECHOSTATUS_NO_MEM;
        
        if ((wPipeOut >= m_wNumPipesOut) || (wBusOut >= m_wNumBussesOut))
        {
                ECHO_DEBUGPRINTF(("CPipeOutCtrl::SetGain - out of range pipe %d bus %d\n",
                                                                wPipeOut,wBusOut));
                return ECHOSTATUS_INVALID_PARAM;                
        }
        
        WORD wIndex = GetIndex(wPipeOut,wBusOut);

        /*
        ECHO_DEBUGPRINTF(("CPipeOutCtrl::SetGain pipe %d  bus %d  gain 0x%lx  index %d\n",
                                                        wPipeOut,wBusOut,iGain,wIndex));
        */
        
        if (ECHOGAIN_UPDATE == iGain)
        {
                iGain = DSP_TO_GENERIC( m_Gains[ wIndex ] );
        }
        else
        {
                if (iGain > ECHOGAIN_MAXOUT)
                        iGain = ECHOGAIN_MAXOUT;
                else if (iGain < ECHOGAIN_MUTED)
                        iGain = ECHOGAIN_MUTED;
        
                m_Gains[ wIndex ] = (INT8) GENERIC_TO_DSP( iGain );
                
                //
                // Store the notify
                //
                m_pEG->MixerControlChanged(ECHO_PIPE_OUT,MXN_LEVEL,wPipeOut,wBusOut);
        }
                
        if (m_fHasVmixer)
        {
                wBusOut &= 0xfffe;
                
                if (NULL == m_Pans)
                        return ECHOSTATUS_NO_MEM;
        
                //
                // For vmixer cards, the DSP handles the output bus gain, 
                // so no need to account for it here.  Vmixer output pipes
                // do have to handle panning.
                //
                INT32 iLeft = iGain + DSP_TO_GENERIC( m_PanDbs[wIndex].iLeft );
                INT32 iRight = iGain + DSP_TO_GENERIC( m_PanDbs[wIndex].iRight );
                
                //
                // Add master gain values
                //
                iBusOutGain = m_pEG->m_BusOutLineLevels[wBusOut].GetGain();
                iLeft += iBusOutGain;
                iBusOutGain = m_pEG->m_BusOutLineLevels[wBusOut+1].GetGain();
                iRight += iBusOutGain;
                
                //
                // Muting and clamping
                //              
                if (m_Mutes[wIndex])
                {
                        iLeft = ECHOGAIN_MUTED;
                        iRight = ECHOGAIN_MUTED;
                }
                else
                {
                        if (  (m_pEG->m_BusOutLineLevels[wBusOut].IsMuteOn()) ||
                                        (iLeft < ECHOGAIN_MUTED))
                        {
                                iLeft = ECHOGAIN_MUTED;
                        }
                        else if (iLeft > ECHOGAIN_MAXOUT)
                        {
                                iLeft = ECHOGAIN_MAXOUT;                                
                        }

                        if (  (m_pEG->m_BusOutLineLevels[wBusOut + 1].IsMuteOn()) ||
                                        (iRight < ECHOGAIN_MUTED))
                        {
                                iRight = ECHOGAIN_MUTED;
                        }
                        else if (iRight > ECHOGAIN_MAXOUT)
                        {
                                iRight = ECHOGAIN_MAXOUT;                               
                        }
                }
                
                //
                // Set the left channel gain
                //
                Status = m_pEG->GetDspCommObject()->SetPipeOutGain(wPipeOut,
                                                                                                                                                        wBusOut,
                                                                                                                                                        iLeft,
                                                                                                                                                        FALSE);
                if (ECHOSTATUS_OK == Status)
                {
                        //
                        // And the right channel
                        //
                        Status = m_pEG->GetDspCommObject()->SetPipeOutGain(wPipeOut,
                                                                                                                                                                wBusOut + 1,
                                                                                                                                                                iRight,
                                                                                                                                                                fImmediate);
                }
                
        }
        else
        {
                //
                // Add this output pipe gain to the output bus gain
                // Since these gains are in decibels, it's OK to just add them
                //
                iBusOutGain = m_pEG->m_BusOutLineLevels[wBusOut].GetGain();
                iGain += iBusOutGain;

                //
                // Mute this output pipe if this output bus is muted
                //      
                if (m_Mutes[ wIndex ] ||
                         (m_pEG->m_BusOutLineLevels[wBusOut].IsMuteOn()) )
                {
                        iGain = ECHOGAIN_MUTED;
                }
                else
                {
                        //
                        // Clamp the output pipe gain if necessary
                        //
                        if (iGain < ECHOGAIN_MUTED)
                                iGain = ECHOGAIN_MUTED;
                        else if (iGain > ECHOGAIN_MAXOUT)
                                iGain = ECHOGAIN_MAXOUT;
                        
                }

                //
                // Set the gain
                //
                Status = m_pEG->GetDspCommObject()->SetPipeOutGain(wPipeOut,
                                                                                                                                                        wBusOut,
                                                                                                                                                        iGain,
                                                                                                                                                        fImmediate);

        }
        
        return Status;
}       


ECHOSTATUS CPipeOutCtrl::GetGain(WORD wPipeOut, WORD wBusOut, INT32 &iGain)
{
        WORD    wIndex = GetIndex(wPipeOut,wBusOut);
        
        if (NULL == m_Gains)
                return ECHOSTATUS_NO_MEM;

        if ((wPipeOut >= m_wNumPipesOut) || (wBusOut >= m_wNumBussesOut))
        {
                ECHO_DEBUGPRINTF(("CPipeOutCtrl::GetGain - out of range pipe %d bus %d\n",
                                                                wPipeOut,wBusOut));
                return ECHOSTATUS_INVALID_PARAM;                
        }

        iGain = DSP_TO_GENERIC( m_Gains[wIndex] );

        /*
        ECHO_DEBUGPRINTF(("CPipeOutCtrl::GetGain pipe %d  bus %d  gain 0x%lx  index %d\n",
                                                        wPipeOut,wBusOut,iGain,wIndex));
        */
                                        
        return ECHOSTATUS_OK;
}       


//*****************************************************************************
//
// Set and get mute
//
//*****************************************************************************

ECHOSTATUS CPipeOutCtrl::SetMute
(
        WORD wPipeOut, 
        WORD wBusOut, 
        BOOL bMute,
        BOOL fImmediate
)
{
        if (!m_fHasVmixer && (wPipeOut != wBusOut))
                return ECHOSTATUS_OK;

        if (NULL == m_Mutes)
                return ECHOSTATUS_NO_MEM;

        if ((wPipeOut >= m_wNumPipesOut) || (wBusOut >= m_wNumBussesOut))
        {
                ECHO_DEBUGPRINTF(("CPipeOutCtrl::SetMute - out of range pipe %d bus %d\n",
                                                                wPipeOut,wBusOut));
                return ECHOSTATUS_INVALID_PARAM;                
        }

        WORD wIndex = GetIndex(wPipeOut,wBusOut);
        
        /*
        ECHO_DEBUGPRINTF(("CPipeOutCtrl::SetMute wPipeOut %d  wBusOut %d  bMute %ld\n",
                                                        wPipeOut,wBusOut,bMute));
        */
        
        //
        // Store the mute
        //
        m_Mutes[ wIndex ] = (BYTE) bMute;

        //
        // Store the notify
        //
        m_pEG->MixerControlChanged(ECHO_PIPE_OUT,MXN_MUTE,wPipeOut,wBusOut);

        //
        // Call the SetGain function to do all the heavy lifting
        // Use the ECHOGAIN_UPDATE value to tell the function to 
        // recalculate the gain setting using the currently stored value.
        //
        return SetGain(wPipeOut,wBusOut,ECHOGAIN_UPDATE,fImmediate);
}       


ECHOSTATUS CPipeOutCtrl::GetMute(WORD wPipeOut, WORD wBusOut, BOOL &bMute)
{
        WORD    wIndex = GetIndex(wPipeOut,wBusOut);
        
        if (NULL == m_Mutes)
                return ECHOSTATUS_NO_MEM;

        if ((wPipeOut >= m_wNumPipesOut) || (wBusOut >= m_wNumBussesOut))
        {
                ECHO_DEBUGPRINTF(("CPipeOutCtrl::GetMute - out of range pipe %d bus %d\n",
                                                                wPipeOut,wBusOut));
                return ECHOSTATUS_INVALID_PARAM;                
        }

        bMute = (BOOL) m_Mutes[ wIndex ];

        /*
        ECHO_DEBUGPRINTF(("CPipeOutCtrl::GetMute wPipeOut %d  wBusOut %d  bMute %ld\n",
                                                        wPipeOut,wBusOut,bMute));
        */

        return ECHOSTATUS_OK;
}       


//*****************************************************************************
//
// Set and get pan (vmixer only)
//
//*****************************************************************************

ECHOSTATUS CPipeOutCtrl::SetPan(WORD wPipeOut, WORD wBusOut, INT32 iPan)
{
        if (!m_fHasVmixer)
                return ECHOSTATUS_OK;
                
        if (NULL == m_Pans)
                return ECHOSTATUS_NO_MEM;
        
        if ((wPipeOut >= m_wNumPipesOut) || (wBusOut >= m_wNumBussesOut))
        {
                ECHO_DEBUGPRINTF(("CPipeOutCtrl::SetPan - out of range pipe %d bus %d\n",
                                                                wPipeOut,wBusOut));
                return ECHOSTATUS_INVALID_PARAM;                
        }
                

        WORD    wIndex = GetIndex(wPipeOut,wBusOut);            

        //
        // Clamp it and stash it
        //              
        if (iPan < 0)
                iPan = 0;
        else if (iPan > MAX_MIXER_PAN)
                iPan = MAX_MIXER_PAN;
                
        m_Pans[wIndex] = (WORD) iPan;

        //
        // Store the notify
        //
        m_pEG->MixerControlChanged(ECHO_PIPE_OUT,MXN_PAN,wPipeOut,wBusOut);

        //
        //      Convert this pan setting into left and right dB values
        //              
        m_PanDbs[wIndex].iLeft = (INT8) GENERIC_TO_DSP( PanToDb(MAX_MIXER_PAN - iPan) );
        m_PanDbs[wIndex].iRight = (INT8) GENERIC_TO_DSP( PanToDb(iPan) );

        //
        // Again, SetGain does all the hard work
        //      
        return SetGain(wPipeOut,wBusOut,ECHOGAIN_UPDATE);
}       


ECHOSTATUS CPipeOutCtrl::GetPan(WORD wPipeOut, WORD wBusOut, INT32 &iPan)
{
        WORD    wIndex = GetIndex(wPipeOut,wBusOut);
        
        if (NULL == m_Pans)
                return ECHOSTATUS_NO_MEM;

        if ((wPipeOut >= m_wNumPipesOut) || (wBusOut >= m_wNumBussesOut))
        {
                ECHO_DEBUGPRINTF(("CPipeOutCtrl::GetPan - out of range pipe %d bus %d\n",
                                                                wPipeOut,wBusOut));
                return ECHOSTATUS_INVALID_PARAM;                
        }

        iPan = m_Pans[ wIndex ];

        return ECHOSTATUS_OK;
}