windows-nt/Source/XPSP1/NT/shell/osshell/control/mmsys/multchan.c
2020-09-26 16:20:57 +08:00

928 lines
28 KiB
C

///////////////////////////////////////////////////////////////////////////////
//
// File: multchan.c
//
// This file defines the functions that drive the multichannel
// volume tab of the Sounds & Multimedia control panel.
//
// History:
// 13 March 2000 RogerW
// Created.
//
// Copyright (C) 2000 Microsoft Corporation All Rights Reserved.
//
// Microsoft Confidential
//
///////////////////////////////////////////////////////////////////////////////
//=============================================================================
// Include files
//=============================================================================
#include <windows.h>
#include <windowsx.h>
#include <tchar.h>
#include <regstr.h>
#include <dbt.h>
#include "medhelp.h"
#include "mmcpl.h"
#include "multchan.h"
#include "speakers.h"
#include "dslevel.h"
// Externals
extern BOOL DeviceChange_GetHandle(DWORD dwMixerID, HANDLE *phDevice);
extern HRESULT DSGetGuidFromName(LPTSTR szName, BOOL fRecord, LPGUID pGuid);
extern HRESULT DSGetCplValues(GUID guid, BOOL fRecord, LPCPLDATA pData);
// Globals
UINT g_uiMCMixID = 0;
HMIXER g_hMCMixer = NULL;
UINT g_uiMCPageStringID = 0;
UINT g_uiMCDescStringID = 0;
LPVOID g_paPrevious = NULL;
BOOL g_fInternalMCGenerated = FALSE;
BOOL g_fMCChanged = FALSE;
MIXERCONTROLDETAILS g_mcdMC;
MIXERLINE g_mlMCDst;
WNDPROC g_fnMCPSProc = NULL;
UINT g_uiMCDevChange = 0;
HWND g_hWndMC = NULL;
static HDEVNOTIFY g_hMCDeviceEventContext= NULL;
// Constants
#define VOLUME_TICS (500) // VOLUME_TICS * VOLUME_MAX must be less than 0xFFFFFFFF
#define VOLUME_MAX (0xFFFF)
#define VOLUME_MIN (0)
#define MC_SLIDER_COUNT (8) // Update Code & Dialog Template if change this value!
static INTCODE aKeyWordIds[] =
{
IDC_MC_DESCRIPTION, NO_HELP,
IDC_MC_ZERO_LOW, IDH_MC_ALL_SLIDERS,
IDC_MC_ZERO, IDH_MC_ALL_SLIDERS,
IDC_MC_ZERO_VOLUME, IDH_MC_ALL_SLIDERS,
IDC_MC_ZERO_HIGH, IDH_MC_ALL_SLIDERS,
IDC_MC_ONE_LOW, IDH_MC_ALL_SLIDERS,
IDC_MC_ONE, IDH_MC_ALL_SLIDERS,
IDC_MC_ONE_VOLUME, IDH_MC_ALL_SLIDERS,
IDC_MC_ONE_HIGH, IDH_MC_ALL_SLIDERS,
IDC_MC_TWO_LOW, IDH_MC_ALL_SLIDERS,
IDC_MC_TWO, IDH_MC_ALL_SLIDERS,
IDC_MC_TWO_VOLUME, IDH_MC_ALL_SLIDERS,
IDC_MC_TWO_HIGH, IDH_MC_ALL_SLIDERS,
IDC_MC_THREE_LOW, IDH_MC_ALL_SLIDERS,
IDC_MC_THREE, IDH_MC_ALL_SLIDERS,
IDC_MC_THREE_VOLUME, IDH_MC_ALL_SLIDERS,
IDC_MC_THREE_HIGH, IDH_MC_ALL_SLIDERS,
IDC_MC_FOUR_LOW, IDH_MC_ALL_SLIDERS,
IDC_MC_FOUR, IDH_MC_ALL_SLIDERS,
IDC_MC_FOUR_VOLUME, IDH_MC_ALL_SLIDERS,
IDC_MC_FOUR_HIGH, IDH_MC_ALL_SLIDERS,
IDC_MC_FIVE_LOW, IDH_MC_ALL_SLIDERS,
IDC_MC_FIVE, IDH_MC_ALL_SLIDERS,
IDC_MC_FIVE_VOLUME, IDH_MC_ALL_SLIDERS,
IDC_MC_FIVE_HIGH, IDH_MC_ALL_SLIDERS,
IDC_MC_SIX_LOW, IDH_MC_ALL_SLIDERS,
IDC_MC_SIX, IDH_MC_ALL_SLIDERS,
IDC_MC_SIX_VOLUME, IDH_MC_ALL_SLIDERS,
IDC_MC_SIX_HIGH, IDH_MC_ALL_SLIDERS,
IDC_MC_SEVEN_LOW, IDH_MC_ALL_SLIDERS,
IDC_MC_SEVEN, IDH_MC_ALL_SLIDERS,
IDC_MC_SEVEN_VOLUME, IDH_MC_ALL_SLIDERS,
IDC_MC_SEVEN_HIGH, IDH_MC_ALL_SLIDERS,
IDC_MC_MOVE_TOGETHER, IDH_MC_MOVE_TOGETHER,
IDC_MC_RESTORE, IDH_MC_RESTORE,
0,0
};
///////////////////////////////////////////////////////////////////////////////
//
// %%Function: MCTabProc
//
// Parameters: hDlg = window handle of dialog window.
// uiMessage = message ID.
// wParam = message-dependent.
// lParam = message-dependent.
//
// Returns: TRUE if message has been processed, else FALSE
//
// Description: Dialog proc for multichannel control panel page device change
// message.
//
//
///////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK MCTabProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
if (iMsg == g_uiMCDevChange)
{
InitMCVolume (g_hWndMC);
}
return CallWindowProc (g_fnMCPSProc, hwnd, iMsg, wParam, lParam);
}
///////////////////////////////////////////////////////////////////////////////
//
// %%Function: MultichannelDlg
//
// Parameters: hDlg = window handle of dialog window.
// uiMessage = message ID.
// wParam = message-dependent.
// lParam = message-dependent.
//
// Returns: TRUE if message has been processed, else FALSE
//
// Description: Dialog proc for multichannel volume control panel page.
//
//
///////////////////////////////////////////////////////////////////////////////
INT_PTR CALLBACK MultichannelDlg (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_NOTIFY:
{
OnNotifyMC (hDlg, (LPNMHDR) lParam);
}
break;
case WM_INITDIALOG:
{
HANDLE_WM_INITDIALOG (hDlg, wParam, lParam, OnInitDialogMC);
}
break;
case WM_DESTROY:
{
HANDLE_WM_DESTROY (hDlg, wParam, lParam, OnDestroyMC);
}
break;
case WM_COMMAND:
{
HANDLE_WM_COMMAND (hDlg, wParam, lParam, OnCommandMC);
}
break;
case WM_HSCROLL:
{
HANDLE_WM_HSCROLL (hDlg, wParam, lParam, MCVolumeScroll);
}
break;
case WM_POWERBROADCAST:
{
HandleMCPowerBroadcast (hDlg, wParam, lParam);
}
break;
case MM_MIXM_LINE_CHANGE:
case MM_MIXM_CONTROL_CHANGE:
{
if (!g_fInternalMCGenerated)
{
DisplayMCVolumeControl (hDlg);
}
g_fInternalMCGenerated = FALSE;
}
break;
case WM_CONTEXTMENU:
{
WinHelp ((HWND)wParam, NULL, HELP_CONTEXTMENU,
(UINT_PTR)(LPTSTR)aKeyWordIds);
}
break;
case WM_HELP:
{
WinHelp (((LPHELPINFO)lParam)->hItemHandle, NULL, HELP_WM_HELP
, (UINT_PTR)(LPTSTR)aKeyWordIds);
}
break;
case WM_DEVICECHANGE:
{
MCDeviceChange_Change (hDlg, wParam, lParam);
}
break;
case WM_WININICHANGE:
case WM_DISPLAYCHANGE :
{
int iLastSliderID = IDC_MC_ZERO_VOLUME + (MC_SLIDER_COUNT - 1) * 4;
int indx = IDC_MC_ZERO_VOLUME;
for (; indx <= iLastSliderID; indx += 4)
SendDlgItemMessage (hDlg, indx, uMsg, wParam, lParam);
}
break;
}
return FALSE;
}
void InitMCVolume (HWND hDlg)
{
FreeMCMixer ();
if (MMSYSERR_NOERROR == mixerOpen (&g_hMCMixer, g_uiMCMixID, (DWORD_PTR) hDlg, 0L, CALLBACK_WINDOW))
{
if (SUCCEEDED (GetMCVolume ()) && g_paPrevious && g_mcdMC.paDetails)
{
// Copy data so can undo volume changes
memcpy (g_paPrevious, g_mcdMC.paDetails, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
DisplayMCVolumeControl (hDlg);
}
MCDeviceChange_Init (hDlg, g_uiMCMixID);
}
}
BOOL OnInitDialogMC (HWND hDlg, HWND hwndFocus, LPARAM lParam)
{
TCHAR szDescription [255];
LoadString (ghInstance, g_uiMCDescStringID, szDescription, sizeof (szDescription)/sizeof (TCHAR));
SetWindowText (GetDlgItem (hDlg, IDC_MC_DESCRIPTION), szDescription);
// Init Globals
g_fInternalMCGenerated = FALSE;
g_fMCChanged = FALSE;
g_hWndMC = hDlg;
// Set Device Change Notification
g_fnMCPSProc = (WNDPROC) SetWindowLongPtr (GetParent (hDlg), GWLP_WNDPROC, (LONG_PTR) MCTabProc);
g_uiMCDevChange = RegisterWindowMessage (_T("winmm_devicechange"));
// Init Volume
InitMCVolume (hDlg);
return FALSE;
}
void OnDestroyMC (HWND hDlg)
{
// Unregister from notifications
MCDeviceChange_Cleanup ();
SetWindowLongPtr (GetParent (hDlg), GWLP_WNDPROC, (LONG_PTR) g_fnMCPSProc);
FreeAll ();
}
void OnNotifyMC (HWND hDlg, LPNMHDR pnmh)
{
if (!pnmh)
{
DPF ("bad WM_NOTIFY pointer\n");
return;
}
switch (pnmh->code)
{
case PSN_KILLACTIVE:
FORWARD_WM_COMMAND (hDlg, IDOK, 0, 0, SendMessage);
break;
case PSN_APPLY:
FORWARD_WM_COMMAND (hDlg, ID_APPLY, 0, 0, SendMessage);
break;
case PSN_RESET:
FORWARD_WM_COMMAND (hDlg, IDCANCEL, 0, 0, SendMessage);
break;
}
}
BOOL PASCAL OnCommandMC (HWND hDlg, int id, HWND hwndCtl, UINT codeNotify)
{
switch (id)
{
case IDC_MC_RESTORE:
{
// Move all sliders to center
UINT uiIndx;
for (uiIndx = 0; uiIndx < g_mlMCDst.cChannels; uiIndx++)
{
((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + uiIndx) -> dwValue = VOLUME_MAX/2;
}
g_fInternalMCGenerated = FALSE;
mixerSetControlDetails ((HMIXEROBJ) g_hMCMixer, &g_mcdMC, MIXER_SETCONTROLDETAILSF_VALUE);
if (!g_fMCChanged)
{
g_fMCChanged = TRUE;
PropSheet_Changed (GetParent (hDlg), hDlg);
}
}
break;
case ID_APPLY:
{
if (SUCCEEDED (GetMCVolume ()) && g_paPrevious && g_mcdMC.paDetails)
{
// Copy data so can undo volume changes
memcpy (g_paPrevious, g_mcdMC.paDetails, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
DisplayMCVolumeControl (hDlg);
}
g_fMCChanged = FALSE;
return TRUE;
}
break;
case IDOK:
{
}
break;
case IDCANCEL:
{
if (g_paPrevious && g_mcdMC.paDetails)
{
// Undo volume changes
memcpy (g_mcdMC.paDetails, g_paPrevious, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
g_fInternalMCGenerated = TRUE;
mixerSetControlDetails ((HMIXEROBJ) g_hMCMixer, &g_mcdMC, MIXER_SETCONTROLDETAILSF_VALUE);
}
WinHelp (hDlg, gszWindowsHlp, HELP_QUIT, 0L);
}
break;
}
return FALSE;
}
void HandleMCPowerBroadcast (HWND hWnd, WPARAM wParam, LPARAM lParam)
{
switch (wParam)
{
case PBT_APMQUERYSUSPEND:
{
FreeMCMixer ();
}
break;
case PBT_APMQUERYSUSPENDFAILED:
case PBT_APMRESUMESUSPEND:
{
InitMCVolume (hWnd);
}
break;
}
}
BOOL SliderIDtoChannel (UINT uiSliderID, DWORD* pdwChannel)
{
if (!pdwChannel)
return FALSE;
// Determine channel number (index)
switch (uiSliderID)
{
case IDC_MC_ZERO_VOLUME: // Left
*pdwChannel = 0;
break;
case IDC_MC_ONE_VOLUME: // Right
*pdwChannel = 1;
break;
case IDC_MC_TWO_VOLUME: // Center
*pdwChannel = 2;
break;
case IDC_MC_THREE_VOLUME: // Back Left
*pdwChannel = 3;
break;
case IDC_MC_FOUR_VOLUME: // Back Right
*pdwChannel = 4;
break;
case IDC_MC_FIVE_VOLUME: // Low Frequency
*pdwChannel = 5;
break;
case IDC_MC_SIX_VOLUME: // Left of Center
*pdwChannel = 6;
break;
case IDC_MC_SEVEN_VOLUME: // Right of Center
*pdwChannel = 7;
break;
default:
return FALSE;
}
return ((*pdwChannel) < g_mlMCDst.cChannels);
}
// Called in response to slider movement, computes new volume level and sets it
// it also controls the apply state (changed or not)
void MCVolumeScroll (HWND hwnd, HWND hwndCtl, UINT code, int pos)
{
BOOL fSet;
BOOL fMoveTogether;
DWORD dwChannel;
DWORD dwSliderVol;
DWORD dwNewMixerVol;
DWORD dwOldMixerVol;
DWORD dwVolume;
fMoveTogether = IsDlgButtonChecked (hwnd, IDC_MC_MOVE_TOGETHER);
if (SliderIDtoChannel (GetDlgCtrlID (hwndCtl), &dwChannel))
{
// Set the new volume
dwSliderVol = (DWORD) SendMessage (hwndCtl, TBM_GETPOS, 0, 0);
dwNewMixerVol = (VOLUME_MAX * dwSliderVol + VOLUME_TICS / 2) / VOLUME_TICS;
dwOldMixerVol = (g_paPrevious ? ((MIXERCONTROLDETAILS_UNSIGNED*)g_paPrevious + dwChannel) -> dwValue : 0);
fSet = SetMCVolume (dwChannel, dwNewMixerVol, fMoveTogether);
if (!fSet)
{
// Restore the correct thumb position.
dwVolume = (VOLUME_TICS * ((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + dwChannel) -> dwValue + VOLUME_MAX / 2) / VOLUME_MAX;
SendMessage (hwndCtl, TBM_SETPOS, TRUE, dwVolume);
}
if ((fMoveTogether || dwOldMixerVol != dwNewMixerVol) && !g_fMCChanged)
{
g_fMCChanged = TRUE;
PropSheet_Changed (GetParent (hwnd), hwnd);
}
}
}
// Sets the volume level
//
BOOL SetMCVolume (DWORD dwChannel, DWORD dwVol, BOOL fMoveTogether)
{
BOOL fReturn;
UINT uiIndx;
DWORD dwValue;
long lMoveValue;
long lMoveValueActual;
fReturn = TRUE;
if (dwChannel < g_mlMCDst.cChannels && g_mcdMC.paDetails && g_hMCMixer)
{
if (fMoveTogether)
{
// Note: Do not set g_fInternalMCGenerated = TRUE here because we are relying
// on the change notification to update the other sliders.
lMoveValue = (long)((double)dwVol - (double)(((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + dwChannel) -> dwValue));
lMoveValueActual = lMoveValue;
// Don't bother if no move requested.
if (lMoveValue == 0)
return TRUE; // Already Set
// Ensure that the new value is within the range of all the sliders that are
// being used. This will ensure that we maintain the distance between sliders
// as they are being moved.
for (uiIndx = 0; uiIndx < g_mlMCDst.cChannels; uiIndx++)
{
dwValue = ((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + uiIndx) -> dwValue;
if (VOLUME_MIN > ((long)dwValue+lMoveValueActual))
{
lMoveValueActual = VOLUME_MIN - dwValue;
}
else
{
if (VOLUME_MAX < ((long)dwValue+lMoveValueActual))
lMoveValueActual = VOLUME_MAX - dwValue;
}
}
if (lMoveValueActual != 0)
{
// Update the values
for (uiIndx = 0; uiIndx < g_mlMCDst.cChannels; uiIndx++)
{
dwValue = ((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + uiIndx) -> dwValue;
((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + uiIndx) -> dwValue = (DWORD)((long) dwValue + lMoveValueActual);
}
}
else
{
// Let user know that they can move no farther in the current direction.
// Note: We use the PC Speaker instead of the mixer because this is an
// indicator that they are at either min or max volume for one of
// the sliders. Since these are channel volume sliders, if we used
// the mixer, the user would either not hear anything (min volume)
// or get blown away (max volume).
MessageBeep (-1 /*PC Speaker*/);
fReturn = FALSE;
}
}
else
{
g_fInternalMCGenerated = TRUE;
((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + dwChannel) -> dwValue = dwVol;
}
mixerSetControlDetails ((HMIXEROBJ) g_hMCMixer, &g_mcdMC, MIXER_SETCONTROLDETAILSF_VALUE);
}
return fReturn;
}
void ShowAndEnableWindow (HWND hwnd, BOOL fEnable)
{
EnableWindow (hwnd, fEnable);
ShowWindow (hwnd, fEnable ? SW_SHOW : SW_HIDE);
}
void DisplayMCVolumeControl (HWND hDlg)
{
HWND hwndVol = NULL;
HWND hwndLabel = NULL;
WCHAR szLabel[MAX_PATH];
BOOL fEnabled;
UINT uiIndx;
DWORD dwSpeakerType = TYPE_STEREODESKTOP;
BOOL fPlayback = (MIXERLINE_COMPONENTTYPE_DST_SPEAKERS == g_mlMCDst.dwComponentType);
ZeroMemory (szLabel, sizeof (szLabel));
// Get Speaker Configuration Type
if (fPlayback)
GetSpeakerType (&dwSpeakerType);
for (uiIndx = 0; uiIndx < MC_SLIDER_COUNT; uiIndx++)
{
fEnabled = (uiIndx < g_mlMCDst.cChannels);
// Set up the volume slider
hwndVol = GetDlgItem (hDlg, IDC_MC_ZERO_VOLUME + uiIndx * 4);
SendMessage (hwndVol, TBM_SETTICFREQ, VOLUME_TICS / 10, 0);
SendMessage (hwndVol, TBM_SETRANGE, FALSE, MAKELONG (0, VOLUME_TICS));
// Enable/Disable sliders
hwndLabel = GetDlgItem (hDlg, IDC_MC_ZERO + uiIndx * 4);
ShowAndEnableWindow (hwndVol, fEnabled);
ShowAndEnableWindow (hwndLabel, fEnabled);
ShowAndEnableWindow (GetDlgItem (hDlg, IDC_MC_ZERO_LOW + uiIndx * 4), fEnabled);
ShowAndEnableWindow (GetDlgItem (hDlg, IDC_MC_ZERO_HIGH + uiIndx * 4), fEnabled);
if (fPlayback)
{
GetSpeakerLabel (dwSpeakerType, uiIndx, szLabel, sizeof(szLabel)/sizeof(TCHAR));
}
else
{
LoadString (ghInstance, IDS_MC_CHANNEL_ZERO + uiIndx, szLabel, MAX_PATH);
}
SetWindowText (hwndLabel, szLabel);
}
if (0 < g_mlMCDst.cChannels)
{
UpdateMCVolumeSliders (hDlg);
}
}
BOOL GetSpeakerType (DWORD* pdwSpeakerType)
{
BOOL fReturn = FALSE;
if (pdwSpeakerType)
{
MIXERCAPS mc;
*pdwSpeakerType = TYPE_STEREODESKTOP; // Init Value
if (MMSYSERR_NOERROR == mixerGetDevCaps (g_uiMCMixID, &mc, sizeof (mc)))
{
GUID guid;
if (SUCCEEDED (DSGetGuidFromName (mc.szPname, FALSE, &guid)))
{
CPLDATA cpldata;
if (SUCCEEDED (DSGetCplValues (guid, FALSE, &cpldata)))
{
*pdwSpeakerType = cpldata.dwSpeakerType;
fReturn = TRUE;
}
}
}
}
return fReturn;
}
BOOL GetSpeakerLabel (DWORD dwSpeakerType, UINT uiSliderIndx, WCHAR* szLabel, int nSize)
{
if (uiSliderIndx >= MC_SLIDER_COUNT || !szLabel || nSize <= 0)
// Invalid
return FALSE;
switch (dwSpeakerType)
{
//
// Mono
//
case TYPE_NOSPEAKERS:
case TYPE_MONOLAPTOP:
if (0 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_CENTER, szLabel, nSize));
break;
//
// Stereo
//
case TYPE_HEADPHONES:
case TYPE_STEREODESKTOP:
case TYPE_STEREOLAPTOP:
case TYPE_STEREOMONITOR:
case TYPE_STEREOCPU:
case TYPE_MOUNTEDSTEREO:
case TYPE_STEREOKEYBOARD:
// Left & Right Channel ...
if (0 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_LEFT, szLabel, nSize));
else if (1 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_RIGHT, szLabel, nSize));
break;
//
// Greater than Stereo
//
case TYPE_SURROUND:
// Left & Right Channel ...
if (0 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_LEFT, szLabel, nSize));
else if (1 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_RIGHT, szLabel, nSize));
// Center Front & Back
else if (2 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_CENTER, szLabel, nSize));
else if (3 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_BACKCENTER, szLabel, nSize));
break;
case TYPE_QUADRAPHONIC:
// Left & Right Channel ...
if (0 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_LEFT, szLabel, nSize));
else if (1 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_RIGHT, szLabel, nSize));
// Back Left & Back Right Channel ...
else if (2 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_BACKLEFT, szLabel, nSize));
else if (3 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_BACKRIGHT, szLabel, nSize));
break;
case TYPE_SURROUND_5_1:
case TYPE_SURROUND_7_1:
// Left & Right Channel ...
if (0 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_LEFT, szLabel, nSize));
else if (1 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_RIGHT, szLabel, nSize));
// Center Channel ...
if (2 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_CENTER, szLabel, nSize));
// Low Frequency ...
if (3 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_LOWFREQUENCY, szLabel, nSize));
// Back Left & Back Right Channel ...
if (4 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_BACKLEFT, szLabel, nSize));
else if (5 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_BACKRIGHT, szLabel, nSize));
if (TYPE_SURROUND_5_1 == dwSpeakerType)
break;
// Left of Center & Right of Center Channel ...
if (6 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_LEFT_OF_CENTER, szLabel, nSize));
else if (7 == uiSliderIndx)
return (LoadString (ghInstance, IDS_MC_SPEAKER_RIGHT_OF_CENTER, szLabel, nSize));
break;
}
// If we are here, we don't know the speaker type or we have too many
// channels for a known type, just use the generic channel text ...
return (LoadString (ghInstance, IDS_MC_CHANNEL_ZERO + uiSliderIndx, szLabel, nSize));
}
// Called to update the slider when the volume is changed externally
//
void UpdateMCVolumeSliders (HWND hDlg)
{
if (g_hMCMixer && g_mcdMC.paDetails && SUCCEEDED (GetMCVolume ()))
{
UINT uiIndx;
DWORD dwVolume;
MIXERCONTROLDETAILS_UNSIGNED mcuVolume;
for (uiIndx = 0; uiIndx < g_mlMCDst.cChannels; uiIndx++)
{
mcuVolume = *((MIXERCONTROLDETAILS_UNSIGNED*)g_mcdMC.paDetails + uiIndx);
dwVolume = (VOLUME_TICS * mcuVolume.dwValue + VOLUME_MAX / 2) / VOLUME_MAX;
SendMessage (GetDlgItem (hDlg, IDC_MC_ZERO_VOLUME + uiIndx * 4), TBM_SETPOS, TRUE, dwVolume);
}
}
}
void FreeAll ()
{
FreeMCMixer ();
if (g_mcdMC.paDetails)
{
LocalFree (g_mcdMC.paDetails);
g_mcdMC.paDetails = NULL;
}
if (g_paPrevious)
{
LocalFree (g_paPrevious);
g_paPrevious = NULL;
}
ZeroMemory (&g_mcdMC, sizeof (g_mcdMC));
ZeroMemory (&g_mlMCDst, sizeof (g_mlMCDst));
}
void FreeMCMixer ()
{
if (g_hMCMixer)
{
mixerClose (g_hMCMixer);
g_hMCMixer = NULL;
}
}
HRESULT SetDevice (UINT uiMixID, DWORD dwDest, DWORD dwVolID)
{
HMIXER hMixer = NULL;
HRESULT hr = E_FAIL;
// Free any current mixer stuff
FreeAll ();
if (MMSYSERR_NOERROR == mixerOpen (&hMixer, uiMixID, 0, 0, MIXER_OBJECTF_MIXER))
{
g_mlMCDst.cbStruct = sizeof (g_mlMCDst);
g_mlMCDst.dwDestination = dwDest;
if (MMSYSERR_NOERROR == mixerGetLineInfo ((HMIXEROBJ) hMixer, &g_mlMCDst, MIXER_GETLINEINFOF_DESTINATION))
{
g_mcdMC.cbStruct = sizeof (g_mcdMC);
g_mcdMC.dwControlID = dwVolID;
g_mcdMC.cChannels = g_mlMCDst.cChannels;
g_mcdMC.hwndOwner = 0;
g_mcdMC.cMultipleItems = 0;
g_mcdMC.cbDetails = sizeof (DWORD); // seems like it would be sizeof(g_mcd),
// but actually, it is the size of a single value
// and is multiplied by channel in the driver.
g_mcdMC.paDetails = (MIXERCONTROLDETAILS_UNSIGNED*) LocalAlloc (LPTR, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
g_paPrevious = (MIXERCONTROLDETAILS_UNSIGNED*) LocalAlloc (LPTR, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
if (g_mcdMC.paDetails && g_paPrevious)
{
hr = S_OK;
// Init our other globals
g_uiMCMixID = uiMixID;
switch (g_mlMCDst.dwComponentType)
{
case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
g_uiMCPageStringID = IDS_MC_PLAYBACK;
g_uiMCDescStringID = IDS_MC_PLAYBACK_DESC;
break;
case MIXERLINE_COMPONENTTYPE_DST_WAVEIN:
case MIXERLINE_COMPONENTTYPE_DST_VOICEIN:
g_uiMCPageStringID = IDS_MC_RECORDING;
g_uiMCDescStringID = IDS_MC_RECORDING_DESC;
break;
default:
g_uiMCPageStringID = IDS_MC_OTHER;
g_uiMCDescStringID = IDS_MC_OTHER_DESC;
break;
}
}
}
mixerClose (hMixer);
}
return hr;
}
HRESULT GetMCVolume ()
{
HRESULT hr = E_FAIL;
if (g_hMCMixer && g_mcdMC.paDetails)
{
ZeroMemory (g_mcdMC.paDetails, sizeof (MIXERCONTROLDETAILS_UNSIGNED) * g_mlMCDst.cChannels);
hr = mixerGetControlDetails ((HMIXEROBJ)g_hMCMixer, &g_mcdMC, MIXER_GETCONTROLDETAILSF_VALUE);
}
return hr;
}
UINT GetPageStringID ()
{
return g_uiMCPageStringID;
}
void MCDeviceChange_Cleanup ()
{
if (g_hMCDeviceEventContext)
{
UnregisterDeviceNotification (g_hMCDeviceEventContext);
g_hMCDeviceEventContext = NULL;
}
}
void MCDeviceChange_Init (HWND hWnd, DWORD dwMixerID)
{
DEV_BROADCAST_HANDLE DevBrodHandle;
HANDLE hMixerDevice=NULL;
//If we had registered already for device notifications, unregister ourselves.
MCDeviceChange_Cleanup();
//If we get the device handle register for device notifications on it.
if(DeviceChange_GetHandle(dwMixerID, &hMixerDevice))
{
memset(&DevBrodHandle, 0, sizeof(DEV_BROADCAST_HANDLE));
DevBrodHandle.dbch_size = sizeof(DEV_BROADCAST_HANDLE);
DevBrodHandle.dbch_devicetype = DBT_DEVTYP_HANDLE;
DevBrodHandle.dbch_handle = hMixerDevice;
g_hMCDeviceEventContext = RegisterDeviceNotification(hWnd, &DevBrodHandle, DEVICE_NOTIFY_WINDOW_HANDLE);
if(hMixerDevice)
{
CloseHandle(hMixerDevice);
hMixerDevice = NULL;
}
}
}
// Handle the case where we need to dump mixer handle so PnP can get rid of a device
// We assume we will get the WINMM_DEVICECHANGE handle when the dust settles after a remove or add
// except for DEVICEQUERYREMOVEFAILED which will not generate that message.
//
void MCDeviceChange_Change (HWND hDlg, WPARAM wParam, LPARAM lParam)
{
PDEV_BROADCAST_HANDLE bh = (PDEV_BROADCAST_HANDLE)lParam;
if(!g_hMCDeviceEventContext || !bh || bh->dbch_devicetype != DBT_DEVTYP_HANDLE)
{
return;
}
switch (wParam)
{
case DBT_DEVICEQUERYREMOVE: // Must free up Mixer if they are trying to remove the device
{
FreeMCMixer ();
}
break;
case DBT_DEVICEQUERYREMOVEFAILED: // Didn't happen, need to re-acquire mixer
{
InitMCVolume (hDlg);
}
break;
}
}