You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
377 lines
11 KiB
377 lines
11 KiB
/*==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 <http://www.gnu.org/licenses/>. |
|
|
|
Additional permissions under GNU GPL version 3 section 7 |
|
|
|
If you modify this Program, or any covered work, by linking or |
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, |
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent |
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK |
|
(or a modified version of those libraries), |
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, |
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG |
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the |
|
licensors of this Program grant you additional |
|
permission to convey the resulting work. Corresponding Source for a |
|
non-source form of such a combination shall include the source code for |
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered |
|
work. |
|
|
|
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 <mmsystem.h> |
|
|
|
|
|
//// 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; |
|
} |
|
|
|
|