/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
// //
// plWinMicLevel - Annoying class to deal with the annoying problem of //
// setting the microphone recording volume in Windows. //
// Yeah, you'd THINK there'd be some easier way... //
// //
//// Notes ///////////////////////////////////////////////////////////////////
// //
// 5.8.2001 - Created by mcn. //
// //
//////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "plWinMicLevel.h"
#include "hsWindows.h"
#include
//// Our Local Static Data ///////////////////////////////////////////////////
int sNumMixers = 0;
HMIXER sMixerHandle = nil;
MIXERCAPS sMixerCaps;
DWORD sMinValue = 0, sMaxValue = 0;
DWORD sVolControlID = 0;
//// Local Static Helpers ////////////////////////////////////////////////////
hsBool IGetMuxMicVolumeControl( void );
hsBool IGetBaseMicVolumeControl( void );
hsBool IGetControlValue( DWORD &value );
hsBool ISetControlValue( DWORD value );
MIXERLINE *IGetLineByType( DWORD type );
MIXERLINE *IGetLineByID( DWORD id );
MIXERCONTROL *IGetControlByType( MIXERLINE *line, DWORD type );
MIXERLINE *IGetMixerSubLineByType( MIXERCONTROL *mux, DWORD type );
//// The Publics /////////////////////////////////////////////////////////////
hsScalar plWinMicLevel::GetLevel( void )
{
if( !CanSetLevel() )
return -1;
DWORD rawValue;
if( !IGetControlValue( rawValue ) )
return -1;
return (hsScalar)( rawValue - sMinValue ) / (hsScalar)( sMaxValue - sMinValue );
}
void plWinMicLevel::SetLevel( hsScalar level )
{
if( !CanSetLevel() )
return;
DWORD rawValue = (DWORD)(( level * ( sMaxValue - sMinValue ) ) + sMinValue);
ISetControlValue( rawValue );
}
hsBool plWinMicLevel::CanSetLevel( void )
{
// Just to init
plWinMicLevel &instance = IGetInstance();
return ( sMixerHandle != nil ) ? true : false;
}
//// Protected Init Stuff ////////////////////////////////////////////////////
plWinMicLevel &plWinMicLevel::IGetInstance( void )
{
static plWinMicLevel sInstance;
return sInstance;
}
plWinMicLevel::plWinMicLevel()
{
sMixerHandle = nil;
memset( &sMixerCaps, 0, sizeof( sMixerCaps ) );
// Get the number of mixers in the system
sNumMixers = ::mixerGetNumDevs();
// So long as we have one, open the first one
if( sNumMixers == 0 )
return;
if( ::mixerOpen( &sMixerHandle, 0,
nil, // Window handle to receive callback messages
nil, MIXER_OBJECTF_MIXER ) != MMSYSERR_NOERROR )
{
sMixerHandle = nil; // Just to be sure
return;
}
if( ::mixerGetDevCaps( (UINT)sMixerHandle, &sMixerCaps, sizeof( sMixerCaps ) ) != MMSYSERR_NOERROR )
{
// Oh well, who cares
}
// Try to get the Mux/mixer-based mic volume control first, since that seems to work better/more often/at all
if( !IGetMuxMicVolumeControl() )
{
// Failed, so try getting the volume control from the base mic-in line
if( !IGetBaseMicVolumeControl() )
{
IShutdown();
return;
}
}
}
plWinMicLevel::~plWinMicLevel()
{
IShutdown();
}
void plWinMicLevel::IShutdown( void )
{
if( sMixerHandle != nil )
::mixerClose( sMixerHandle );
sMixerHandle = nil;
}
//// IGetMuxMicVolumeControl /////////////////////////////////////////////////
// Tries to get the volume control of the microphone subline of the MUX or
// mixer control of the WaveIn destination line of the audio system (whew!)
// Note: testing indcates that this works but the direct SRC_MICROPHONE
// doesn't, hence we try this one first.
hsBool IGetMuxMicVolumeControl( void )
{
if( sMixerHandle == nil )
return false;
// Get the WaveIn destination line
MIXERLINE *waveInLine = IGetLineByType( MIXERLINE_COMPONENTTYPE_DST_WAVEIN );
if( waveInLine == nil )
return false;
// Get the mixer or MUX controller from the line
MIXERCONTROL *control = IGetControlByType( waveInLine, MIXERCONTROL_CONTROLTYPE_MIXER );
if( control == nil )
control = IGetControlByType( waveInLine, MIXERCONTROL_CONTROLTYPE_MUX );
if( control == nil )
return false;
// Get the microphone sub-component
// Note: this eventually calls IGetLineByType(), which destroys the waveInLine pointer we had before
MIXERLINE *micLine = IGetMixerSubLineByType( control, MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE );
if( micLine == nil )
return false;
// Get the volume subcontroller
MIXERCONTROL *micVolCtrl = IGetControlByType( micLine, MIXERCONTROL_CONTROLTYPE_VOLUME );
if( micVolCtrl == nil )
return false;
// Found it! store our values
char *dbgLineName = micLine->szName;
char *dbgControlName = micVolCtrl->szName;
sMinValue = micVolCtrl->Bounds.dwMinimum;
sMaxValue = micVolCtrl->Bounds.dwMaximum;
sVolControlID = micVolCtrl->dwControlID;
return true;
}
//// IGetBaseMicVolumeControl ////////////////////////////////////////////////
// Tries to get the volume control of the mic-in line. See
// IGetMuxMicVolumeControl for why we don't do this one first.
hsBool IGetBaseMicVolumeControl( void )
{
if( sMixerHandle == nil )
return false;
// Get the mic source line
MIXERLINE *micLine = IGetLineByType( MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE );
if( micLine == nil )
return false;
// Get the volume subcontroller
MIXERCONTROL *micVolCtrl = IGetControlByType( micLine, MIXERCONTROL_CONTROLTYPE_VOLUME );
if( micVolCtrl == nil )
return false;
// Found it! store our values
char *dbgLineName = micLine->szName;
char *dbgControlName = micVolCtrl->szName;
sMinValue = micVolCtrl->Bounds.dwMinimum;
sMaxValue = micVolCtrl->Bounds.dwMaximum;
sVolControlID = micVolCtrl->dwControlID;
return true;
}
//// IGetControlValue ////////////////////////////////////////////////////////
// Gets the raw value of the current volume control.
hsBool IGetControlValue( DWORD &value )
{
if( sMixerHandle == nil )
return false;
MIXERCONTROLDETAILS_UNSIGNED mxcdVolume;
MIXERCONTROLDETAILS mxcd;
mxcd.cbStruct = sizeof( MIXERCONTROLDETAILS );
mxcd.dwControlID = sVolControlID;
mxcd.cChannels = 1;
mxcd.cMultipleItems = 0;
mxcd.cbDetails = sizeof( MIXERCONTROLDETAILS_UNSIGNED );
mxcd.paDetails = &mxcdVolume;
if( ::mixerGetControlDetails( (HMIXEROBJ)sMixerHandle, &mxcd,
MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE ) != MMSYSERR_NOERROR )
return false;
value = mxcdVolume.dwValue;
return true;
}
//// ISetControlValue ////////////////////////////////////////////////////////
// Sets the raw value of the current volume control.
hsBool ISetControlValue( DWORD value )
{
if( sMixerHandle == nil )
return false;
MIXERCONTROLDETAILS_UNSIGNED mxcdVolume = { value };
MIXERCONTROLDETAILS mxcd;
mxcd.cbStruct = sizeof( MIXERCONTROLDETAILS );
mxcd.dwControlID = sVolControlID;
mxcd.cChannels = 1;
mxcd.cMultipleItems = 0;
mxcd.cbDetails = sizeof( MIXERCONTROLDETAILS_UNSIGNED );
mxcd.paDetails = &mxcdVolume;
if( ::mixerSetControlDetails( (HMIXEROBJ)sMixerHandle, &mxcd,
MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE ) != MMSYSERR_NOERROR )
return false;
return true;
}
//// Helper Functions ////////////////////////////////////////////////////////
MIXERLINE *IGetLineByType( DWORD type )
{
static MIXERLINE mxl;
mxl.cbStruct = sizeof( MIXERLINE );
mxl.dwComponentType = type;
if( ::mixerGetLineInfo( (HMIXEROBJ)sMixerHandle, &mxl, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE ) != MMSYSERR_NOERROR )
return nil;
return &mxl;
}
MIXERLINE *IGetLineByID( DWORD id )
{
static MIXERLINE mxl;
mxl.cbStruct = sizeof( MIXERLINE );
mxl.dwLineID = id;
if( ::mixerGetLineInfo( (HMIXEROBJ)sMixerHandle, &mxl, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_LINEID ) != MMSYSERR_NOERROR )
return nil;
return &mxl;
}
MIXERCONTROL *IGetControlByType( MIXERLINE *line, DWORD type )
{
static MIXERCONTROL mxc;
MIXERLINECONTROLS mxlc;
mxlc.cbStruct = sizeof( MIXERLINECONTROLS );
mxlc.dwLineID = line->dwLineID;
mxlc.dwControlType = type;
mxlc.cControls = 1;
mxlc.cbmxctrl = sizeof( MIXERCONTROL );
mxlc.pamxctrl = &mxc;
if( ::mixerGetLineControls( (HMIXEROBJ)sMixerHandle, &mxlc, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE ) != MMSYSERR_NOERROR )
return nil;
return &mxc;
}
MIXERLINE *IGetMixerSubLineByType( MIXERCONTROL *mux, DWORD type )
{
// A mixer or MUX is really a combination of MORE lines. And beautifully, you can't
// just ask for a single one off of it, you have to ask for them all and search through yourself
MIXERCONTROLDETAILS_LISTTEXT *lineInfo = TRACKED_NEW MIXERCONTROLDETAILS_LISTTEXT[ mux->cMultipleItems ];
if( lineInfo == nil )
return nil;
MIXERCONTROLDETAILS details;
details.cbStruct = sizeof( MIXERCONTROLDETAILS );
details.dwControlID = mux->dwControlID;
details.cChannels = 1;
details.cMultipleItems = mux->cMultipleItems;
details.cbDetails = sizeof( MIXERCONTROLDETAILS_LISTTEXT );
details.paDetails = lineInfo;
if( ::mixerGetControlDetails( (HMIXEROBJ)sMixerHandle, &details, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_LISTTEXT ) != MMSYSERR_NOERROR )
{
delete [] lineInfo;
return nil;
}
// Loop through and find the one with the right component type. But of course it doesn't give us that offhand...
for( unsigned int i = 0; i < mux->cMultipleItems; i++ )
{
MIXERLINE *line = IGetLineByID( lineInfo[ i ].dwParam1 );
if( line->dwComponentType == type )
{
delete [] lineInfo;
return line;
}
}
delete [] lineInfo;
return nil;
}