//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include <windows.h>
#include <mmsystem.h>
#include <malloc.h>
#include "voice_mixer_controls.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


class CMixerControls : public IMixerControls
{
public:
					CMixerControls();
	virtual			~CMixerControls();

	virtual void	Release();
	virtual bool	GetValue_Float(Control iControl, float &value);
	virtual bool	SetValue_Float(Control iControl, float value);
	virtual bool	SelectMicrophoneForWaveInput();


private:
	bool			Init();
	void			Term();

	void			Clear();

	bool			GetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, bool &bValue);
	bool			SetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, const bool bValue);

	bool			GetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, DWORD &value);
	bool			SetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, const DWORD value);

	bool			GetLineControls( DWORD dwLineID, MIXERCONTROL *controls, DWORD nControls );
	void			FindMicSelectControl( DWORD dwLineID, DWORD nControls );
	

private:
	HMIXER			m_hMixer;

	class ControlInfo
	{
	public:
		DWORD	m_dwControlID;
		DWORD	m_cMultipleItems;
		bool	m_bFound;
	};

	DWORD			m_dwMicSelectControlID;
	DWORD			m_dwMicSelectMultipleItems;
	DWORD			m_dwMicSelectControlType;
	DWORD			m_dwMicSelectIndex;
	
	// Info about the controls we found.
	ControlInfo		m_ControlInfos[Control::NumControls];
};



CMixerControls::CMixerControls()
{
	m_dwMicSelectControlID		= 0xFFFFFFFF;

	Clear();
	Init();
}

CMixerControls::~CMixerControls()
{
	Term();
}

bool CMixerControls::Init()
{
	Term();


	MMRESULT mmr;

	
	// Open the mixer.
	mmr = mixerOpen(&m_hMixer, (DWORD)0, 0, 0, 0);
	if(mmr != MMSYSERR_NOERROR)
	{
		Term();
		return false;
	}

	// Iterate over each destination line, looking for Play Controls.
    MIXERCAPS mxcaps;
	mmr = mixerGetDevCaps((UINT)m_hMixer, &mxcaps, sizeof(mxcaps));
	if(mmr != MMSYSERR_NOERROR)
	{
		Term();
		return false;
	}

    for(UINT u = 0; u < mxcaps.cDestinations; u++)
    {
        MIXERLINE recordLine;
		recordLine.cbStruct      = sizeof(recordLine);
        recordLine.dwDestination = u;
        mmr = mixerGetLineInfo((HMIXEROBJ)m_hMixer, &recordLine, MIXER_GETLINEINFOF_DESTINATION);
		if(mmr != MMSYSERR_NOERROR)
			continue;


		// Go through the controls that aren't attached to a specific src connection.
		// We're looking for the checkbox that enables the user's microphone for waveIn.
		if( recordLine.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_WAVEIN )
		{
			FindMicSelectControl( recordLine.dwLineID, recordLine.cControls );
		}


		// Now iterate over each connection (things like wave out, microphone, speaker, CD audio), looking for Microphone.
		UINT cConnections = (UINT)recordLine.cConnections;
        for (UINT v = 0; v < cConnections; v++)
        {
            MIXERLINE micLine;
			micLine.cbStruct      = sizeof(micLine);
            micLine.dwDestination = u;
            micLine.dwSource      = v;

            mmr = mixerGetLineInfo((HMIXEROBJ)m_hMixer, &micLine, MIXER_GETLINEINFOF_SOURCE);
			if(mmr != MMSYSERR_NOERROR)
				continue;

			// Now look at all the controls (volume, mute, boost, etc).
			MIXERCONTROL *controls = (MIXERCONTROL*)_alloca( sizeof(MIXERCONTROL) * micLine.cControls );
			if( !GetLineControls( micLine.dwLineID, controls, micLine.cControls ) )
				continue;

			for(UINT i=0; i < micLine.cControls; i++)
			{
				MIXERCONTROL *pControl = &controls[i];

				if(micLine.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)
				{
					if(pControl->dwControlType == MIXERCONTROL_CONTROLTYPE_ONOFF &&
						(
							strstr(pControl->szShortName, "Gain") || 
							strstr(pControl->szShortName, "Boost") || 
							strstr(pControl->szShortName, "+20d"
						)
					))
					{
						// This is the (record) boost option.
						m_ControlInfos[Control::MicBoost].m_bFound = true;
						m_ControlInfos[Control::MicBoost].m_dwControlID = pControl->dwControlID;
						m_ControlInfos[Control::MicBoost].m_cMultipleItems = pControl->cMultipleItems;
					}

					if(recordLine.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_SPEAKERS &&
						pControl->dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE)
					{
						// This is the mute button.
						m_ControlInfos[Control::MicMute].m_bFound = true;
						m_ControlInfos[Control::MicMute].m_dwControlID = pControl->dwControlID;
						m_ControlInfos[Control::MicMute].m_cMultipleItems = pControl->cMultipleItems;
					}
					
					if(recordLine.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_WAVEIN &&
						pControl->dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME)
					{
						// This is the mic input level.
						m_ControlInfos[Control::MicVolume].m_bFound = true;
						m_ControlInfos[Control::MicVolume].m_dwControlID = pControl->dwControlID;
						m_ControlInfos[Control::MicVolume].m_cMultipleItems = pControl->cMultipleItems;
					}
				}
			}
		}
	}

	return true;
}

void CMixerControls::Term()
{
	if(m_hMixer)
	{
		mixerClose(m_hMixer);
		m_hMixer = 0;
	}

	Clear();
}


bool CMixerControls::GetValue_Float(Control iControl, float &value)
{
	if(iControl < 0 || iControl >= Control::NumControls || !m_ControlInfos[iControl].m_bFound)
		return false;

	if(iControl == Control::MicBoost || iControl == Control::MicMute)
	{
		bool bValue = false;
		bool ret = GetControlOption_Bool(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, bValue);
		value = (float)bValue;
		return ret;
	}
	else if(iControl == Control::MicVolume)
	{
		DWORD dwValue = (DWORD)0;
		if(GetControlOption_Unsigned(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, dwValue))
		{
			value = dwValue / 65535.0f;
			return true;
		}
	}
	
	return false;
}


bool CMixerControls::SetValue_Float(Control iControl, float value)
{
	if(iControl < 0 || iControl >= Control::NumControls || !m_ControlInfos[iControl].m_bFound)
		return false;

	if(iControl == Control::MicBoost || iControl == Control::MicMute)
	{
		bool bValue = !!value;
		return SetControlOption_Bool(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, bValue);
	}
	else if(iControl == Control::MicVolume)
	{
		DWORD dwValue = (DWORD)(value * 65535.0f);
		return SetControlOption_Unsigned(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, dwValue);
	}
	else
	{
		return false;
	}
}


bool CMixerControls::SelectMicrophoneForWaveInput()
{
	if( m_dwMicSelectControlID == 0xFFFFFFFF )
		return false;

	MIXERCONTROLDETAILS_BOOLEAN *pmxcdSelectValue = 
		(MIXERCONTROLDETAILS_BOOLEAN*)_alloca( sizeof(MIXERCONTROLDETAILS_BOOLEAN) * m_dwMicSelectMultipleItems );

	MIXERCONTROLDETAILS mxcd;
	mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
	mxcd.dwControlID = m_dwMicSelectControlID;
	mxcd.cChannels = 1;
	mxcd.cMultipleItems = m_dwMicSelectMultipleItems;
	mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
	mxcd.paDetails = pmxcdSelectValue;
	if (mixerGetControlDetails(reinterpret_cast<HMIXEROBJ>(m_hMixer),
								 &mxcd,
								 MIXER_OBJECTF_HMIXER |
								 MIXER_GETCONTROLDETAILSF_VALUE)
		== MMSYSERR_NOERROR)
	{
		// MUX restricts the line selection to one source line at a time.
		if( m_dwMicSelectControlType == MIXERCONTROL_CONTROLTYPE_MUX )
		{
			ZeroMemory(pmxcdSelectValue,
						 m_dwMicSelectMultipleItems * sizeof(MIXERCONTROLDETAILS_BOOLEAN));
		}

		// set the Microphone value
		pmxcdSelectValue[m_dwMicSelectIndex].fValue = 1;

		mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
		mxcd.dwControlID = m_dwMicSelectControlID;
		mxcd.cChannels = 1;
		mxcd.cMultipleItems = m_dwMicSelectMultipleItems;
		mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
		mxcd.paDetails = pmxcdSelectValue;
		if (mixerSetControlDetails(reinterpret_cast<HMIXEROBJ>(m_hMixer),
									 &mxcd,
									 MIXER_OBJECTF_HMIXER |
									 MIXER_SETCONTROLDETAILSF_VALUE) == MMSYSERR_NOERROR)
		{
			return true;
		}
	}
	

	return false;
}


void CMixerControls::Release()
{
	delete this;
}

void CMixerControls::Clear()
{ 
	m_hMixer = 0;
	memset(m_ControlInfos, 0, sizeof(m_ControlInfos));
}

bool CMixerControls::GetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, bool &bValue)
{
	MIXERCONTROLDETAILS details;
	MIXERCONTROLDETAILS_BOOLEAN controlValue;

	details.cbStruct       = sizeof(details);
	details.dwControlID    = dwControlID;
	details.cChannels      = 1;							// uniform..
	details.cMultipleItems = cMultipleItems;
	details.cbDetails      = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
	details.paDetails      = &controlValue;
	
	MMRESULT mmr = mixerGetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L);
	if(mmr == MMSYSERR_NOERROR)
	{
		bValue = !!controlValue.fValue;
		return true;
	}
	else
	{
		return false;
	}
}

bool CMixerControls::SetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, const bool bValue)
{
	MIXERCONTROLDETAILS details;
	MIXERCONTROLDETAILS_BOOLEAN controlValue;

	details.cbStruct       = sizeof(details);
	details.dwControlID    = dwControlID;
	details.cChannels      = 1;							// uniform..
	details.cMultipleItems = cMultipleItems;
	details.cbDetails      = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
	details.paDetails      = &controlValue;

	controlValue.fValue = bValue;
	
	MMRESULT mmr = mixerSetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L);
	return mmr == MMSYSERR_NOERROR;
}

bool CMixerControls::GetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, DWORD &value)
{
	MIXERCONTROLDETAILS details;
	MIXERCONTROLDETAILS_UNSIGNED controlValue;

	details.cbStruct       = sizeof(details);
	details.dwControlID    = dwControlID;
	details.cChannels      = 1;							// uniform..
	details.cMultipleItems = cMultipleItems;
	details.cbDetails      = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
	details.paDetails      = &controlValue;
	
	MMRESULT mmr = mixerGetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L);
	if(mmr == MMSYSERR_NOERROR)
	{
		value = controlValue.dwValue;
		return true;
	}
	else
	{
		return false;
	}
}

bool CMixerControls::SetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, const DWORD value)
{
	MIXERCONTROLDETAILS details;
	MIXERCONTROLDETAILS_UNSIGNED controlValue;

	details.cbStruct       = sizeof(details);
	details.dwControlID    = dwControlID;
	details.cChannels      = 1;							// uniform..
	details.cMultipleItems = cMultipleItems;
	details.cbDetails      = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
	details.paDetails      = &controlValue;

	controlValue.dwValue = value;
	
	MMRESULT mmr = mixerSetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L);
	return mmr == MMSYSERR_NOERROR;
}


bool CMixerControls::GetLineControls( DWORD dwLineID, MIXERCONTROL *controls, DWORD nControls )
{
	MIXERLINECONTROLS mxlc;

	mxlc.cbStruct = sizeof(mxlc);
	mxlc.dwLineID = dwLineID;
	mxlc.cControls = nControls;
	mxlc.cbmxctrl = sizeof(MIXERCONTROL);
	mxlc.pamxctrl = controls;
	
	MMRESULT mmr = mixerGetLineControls((HMIXEROBJ)m_hMixer, &mxlc, MIXER_GETLINECONTROLSF_ALL);
	return mmr == MMSYSERR_NOERROR;
}


void CMixerControls::FindMicSelectControl( DWORD dwLineID, DWORD nControls )
{
	m_dwMicSelectControlID = 0xFFFFFFFF;

	MIXERCONTROL *recControls = (MIXERCONTROL*)_alloca( sizeof(MIXERCONTROL) * nControls );
	if( !GetLineControls( dwLineID, recControls, nControls ) )
		return;

	for( UINT iRecControl=0; iRecControl < nControls; iRecControl++ )
	{
		if( recControls[iRecControl].dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER ||
			recControls[iRecControl].dwControlType == MIXERCONTROL_CONTROLTYPE_MUX )
		{
			m_dwMicSelectControlID = recControls[iRecControl].dwControlID;
			m_dwMicSelectControlType = recControls[iRecControl].dwControlType;
			m_dwMicSelectMultipleItems = recControls[iRecControl].cMultipleItems;
			m_dwMicSelectIndex = iRecControl;

			// Get the index of the one that selects the mic.
			MIXERCONTROLDETAILS_LISTTEXT *pmxcdSelectText =
				(MIXERCONTROLDETAILS_LISTTEXT*)_alloca( sizeof(MIXERCONTROLDETAILS_LISTTEXT) * m_dwMicSelectMultipleItems );

			MIXERCONTROLDETAILS mxcd;
			mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
			mxcd.dwControlID = m_dwMicSelectControlID;
			mxcd.cChannels = 1;
			mxcd.cMultipleItems = m_dwMicSelectMultipleItems;
			mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_LISTTEXT);
			mxcd.paDetails = pmxcdSelectText;
			
			if (mixerGetControlDetails((HMIXEROBJ)m_hMixer,
										 &mxcd,
										 MIXER_OBJECTF_HMIXER |
										 MIXER_GETCONTROLDETAILSF_LISTTEXT) == MMSYSERR_NOERROR)
			{
				// determine which controls the Microphone source line
				for (DWORD dwi = 0; dwi < m_dwMicSelectMultipleItems; dwi++)
				{
					// get the line information
					MIXERLINE mxl;
					mxl.cbStruct = sizeof(MIXERLINE);
					mxl.dwLineID = pmxcdSelectText[dwi].dwParam1;
					
					if (mixerGetLineInfo((HMIXEROBJ)m_hMixer,
										   &mxl,
										   MIXER_OBJECTF_HMIXER |
										   MIXER_GETLINEINFOF_LINEID) == MMSYSERR_NOERROR &&
						mxl.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)
					{
						// found, dwi is the index.
						m_dwMicSelectIndex = dwi;
						break;
					}
				}
			}

			break;
		}
	}
}


IMixerControls* GetMixerControls()
{
	return new CMixerControls;
}



