mirror of
https://github.com/reactos/reactos.git
synced 2024-08-26 13:18:51 +00:00
650 lines
17 KiB
C
650 lines
17 KiB
C
/*
|
|
* ReactOS Sound Volume Control
|
|
* Copyright (C) 2004-2005 Thomas Weidenmueller
|
|
*
|
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
/*
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
* PROJECT: ReactOS Sound Volume Control
|
|
* FILE: base/applications/sndvol32/mixer.c
|
|
* PROGRAMMERS: Thomas Weidenmueller <w3seek@reactos.com>
|
|
*/
|
|
|
|
#include "sndvol32.h"
|
|
|
|
#define NO_MIXER_SELECTED ((UINT)(~0))
|
|
|
|
static VOID
|
|
ClearMixerCache(PSND_MIXER Mixer)
|
|
{
|
|
PSND_MIXER_DESTINATION Line, NextLine;
|
|
PSND_MIXER_CONNECTION Con, NextCon;
|
|
|
|
for (Line = Mixer->Lines; Line != NULL; Line = NextLine)
|
|
{
|
|
if (Line->Controls != NULL)
|
|
{
|
|
HeapFree(GetProcessHeap(),
|
|
0,
|
|
Line->Controls);
|
|
}
|
|
|
|
for (Con = Line->Connections; Con != NULL; Con = NextCon)
|
|
{
|
|
if (Con->Controls != NULL)
|
|
{
|
|
HeapFree(GetProcessHeap(),
|
|
0,
|
|
Con->Controls);
|
|
}
|
|
|
|
NextCon = Con->Next;
|
|
HeapFree(GetProcessHeap(),
|
|
0,
|
|
Con);
|
|
}
|
|
|
|
NextLine = Line->Next;
|
|
HeapFree(GetProcessHeap(),
|
|
0,
|
|
Line);
|
|
}
|
|
Mixer->Lines = NULL;
|
|
}
|
|
|
|
PSND_MIXER
|
|
SndMixerCreate(HWND hWndNotification, UINT MixerId)
|
|
{
|
|
PSND_MIXER Mixer = (PSND_MIXER) HeapAlloc(GetProcessHeap(),
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(SND_MIXER));
|
|
if (Mixer != NULL)
|
|
{
|
|
Mixer->hWndNotification = hWndNotification;
|
|
Mixer->MixersCount = mixerGetNumDevs();
|
|
Mixer->MixerId = NO_MIXER_SELECTED;
|
|
|
|
if (Mixer->MixersCount > 0)
|
|
{
|
|
/* select the first mixer by default */
|
|
SndMixerSelect(Mixer, MixerId);
|
|
}
|
|
}
|
|
|
|
return Mixer;
|
|
}
|
|
|
|
VOID
|
|
SndMixerDestroy(PSND_MIXER Mixer)
|
|
{
|
|
ClearMixerCache(Mixer);
|
|
SndMixerClose(Mixer);
|
|
HeapFree(GetProcessHeap(),
|
|
0,
|
|
Mixer);
|
|
}
|
|
|
|
VOID
|
|
SndMixerClose(PSND_MIXER Mixer)
|
|
{
|
|
if (Mixer->hmx != NULL)
|
|
{
|
|
mixerClose(Mixer->hmx);
|
|
Mixer->hmx = NULL;
|
|
Mixer->MixerId = NO_MIXER_SELECTED;
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
SndMixerQueryControls(PSND_MIXER Mixer,
|
|
PUINT DisplayControls,
|
|
LPMIXERLINE LineInfo,
|
|
LPMIXERCONTROL *Controls)
|
|
{
|
|
if (LineInfo->cControls > 0)
|
|
{
|
|
*Controls = (MIXERCONTROL*) HeapAlloc(GetProcessHeap(),
|
|
HEAP_ZERO_MEMORY,
|
|
LineInfo->cControls * sizeof(MIXERCONTROL));
|
|
if (*Controls != NULL)
|
|
{
|
|
MIXERLINECONTROLS LineControls;
|
|
MMRESULT Result;
|
|
UINT j;
|
|
|
|
LineControls.cbStruct = sizeof(LineControls);
|
|
LineControls.dwLineID = LineInfo->dwLineID;
|
|
LineControls.cControls = LineInfo->cControls;
|
|
LineControls.cbmxctrl = sizeof(MIXERCONTROL);
|
|
LineControls.pamxctrl = (MIXERCONTROL*)(*Controls);
|
|
|
|
Result = mixerGetLineControls((HMIXEROBJ)Mixer->hmx,
|
|
&LineControls,
|
|
MIXER_GETLINECONTROLSF_ALL);
|
|
if (Result == MMSYSERR_NOERROR)
|
|
{
|
|
for (j = 0; j < LineControls.cControls; j++)
|
|
{
|
|
if (SndMixerIsDisplayControl(Mixer,
|
|
&(*Controls)[j]))
|
|
{
|
|
(*DisplayControls)++;
|
|
}
|
|
|
|
DPRINT("Line control: %ws (0x%x, 0x%x)\n", (*Controls)[j].szName, (*Controls)[j].fdwControl, (*Controls)[j].dwControlType);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
HeapFree(GetProcessHeap(),
|
|
0,
|
|
*Controls);
|
|
*Controls = NULL;
|
|
DPRINT("Failed to get line (ID: 0x%x) controls: %d\n", LineInfo->dwLineID, Result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPRINT("Failed to allocate memory for %d line (ID: 0x%x) controls!\n", LineInfo->dwLineID, LineInfo->cControls);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static BOOL
|
|
SndMixerQueryConnections(PSND_MIXER Mixer,
|
|
PSND_MIXER_DESTINATION Line)
|
|
{
|
|
UINT i, DispControls;
|
|
MIXERLINE LineInfo;
|
|
MMRESULT Result;
|
|
BOOL Ret = TRUE;
|
|
|
|
LineInfo.cbStruct = sizeof(LineInfo);
|
|
for (i = Line->Info.cConnections; i > 0; i--)
|
|
{
|
|
LineInfo.dwDestination = Line->Info.dwDestination;
|
|
LineInfo.dwSource = i - 1;
|
|
Result = mixerGetLineInfo((HMIXEROBJ)Mixer->hmx,
|
|
&LineInfo,
|
|
MIXER_GETLINEINFOF_SOURCE);
|
|
if (Result == MMSYSERR_NOERROR)
|
|
{
|
|
LPMIXERCONTROL Controls = NULL;
|
|
PSND_MIXER_CONNECTION Con;
|
|
|
|
DPRINT("++ Source: %ws\n", LineInfo.szName);
|
|
|
|
DispControls = 0;
|
|
|
|
if (!SndMixerQueryControls(Mixer,
|
|
&DispControls,
|
|
&LineInfo,
|
|
&Controls))
|
|
{
|
|
DPRINT("Failed to query connection controls\n");
|
|
Ret = FALSE;
|
|
break;
|
|
}
|
|
|
|
Con = (SND_MIXER_CONNECTION*) HeapAlloc(GetProcessHeap(),
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(SND_MIXER_CONNECTION));
|
|
if (Con != NULL)
|
|
{
|
|
Con->Info = LineInfo;
|
|
Con->Controls = Controls;
|
|
Con->DisplayControls = DispControls;
|
|
Con->Next = Line->Connections;
|
|
Line->Connections = Con;
|
|
}
|
|
else
|
|
{
|
|
HeapFree(GetProcessHeap(),
|
|
0,
|
|
Controls);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPRINT("Failed to get connection information: %d\n", Result);
|
|
Ret = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Ret;
|
|
}
|
|
|
|
static BOOL
|
|
SndMixerQueryDestinations(PSND_MIXER Mixer)
|
|
{
|
|
UINT i;
|
|
BOOL Ret = TRUE;
|
|
|
|
for (i = Mixer->Caps.cDestinations; i > 0; i--)
|
|
{
|
|
PSND_MIXER_DESTINATION Line;
|
|
|
|
Line = (SND_MIXER_DESTINATION*) HeapAlloc(GetProcessHeap(),
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(SND_MIXER_DESTINATION));
|
|
if (Line != NULL)
|
|
{
|
|
Line->Info.cbStruct = sizeof(Line->Info);
|
|
Line->Info.dwDestination = i - 1;
|
|
if (mixerGetLineInfo((HMIXEROBJ)Mixer->hmx,
|
|
&Line->Info,
|
|
MIXER_GETLINEINFOF_DESTINATION) == MMSYSERR_NOERROR)
|
|
{
|
|
DPRINT("+ Destination: %ws (0x%x, %d)\n", Line->Info.szName, Line->Info.dwLineID, Line->Info.dwComponentType);
|
|
|
|
if (!SndMixerQueryControls(Mixer,
|
|
&Line->DisplayControls,
|
|
&Line->Info,
|
|
&Line->Controls))
|
|
{
|
|
DPRINT("Failed to query mixer controls!\n");
|
|
Ret = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (!SndMixerQueryConnections(Mixer, Line))
|
|
{
|
|
DPRINT("Failed to query mixer connections!\n");
|
|
Ret = FALSE;
|
|
break;
|
|
}
|
|
|
|
Line->Next = Mixer->Lines;
|
|
Mixer->Lines = Line;
|
|
}
|
|
else
|
|
{
|
|
DPRINT("Failed to get line information for id %d!\n", i);
|
|
HeapFree(GetProcessHeap(),
|
|
0,
|
|
Line);
|
|
Ret = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPRINT("Allocation of SND_MIXER_DEST structure for id %d failed!\n", i);
|
|
Ret = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Ret;
|
|
}
|
|
|
|
BOOL
|
|
SndMixerSelect(PSND_MIXER Mixer,
|
|
UINT MixerId)
|
|
{
|
|
if (MixerId >= Mixer->MixersCount)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
SndMixerClose(Mixer);
|
|
|
|
if (mixerOpen(&Mixer->hmx,
|
|
MixerId,
|
|
(DWORD_PTR)Mixer->hWndNotification,
|
|
0,
|
|
CALLBACK_WINDOW | MIXER_OBJECTF_MIXER) == MMSYSERR_NOERROR ||
|
|
mixerOpen(&Mixer->hmx,
|
|
MixerId,
|
|
(DWORD_PTR)Mixer->hWndNotification,
|
|
0,
|
|
CALLBACK_WINDOW) == MMSYSERR_NOERROR ||
|
|
mixerOpen(&Mixer->hmx,
|
|
MixerId,
|
|
0,
|
|
0,
|
|
0) == MMSYSERR_NOERROR)
|
|
{
|
|
if (mixerGetDevCaps(MixerId,
|
|
&Mixer->Caps,
|
|
sizeof(Mixer->Caps)) == MMSYSERR_NOERROR)
|
|
{
|
|
BOOL Ret = FALSE;
|
|
|
|
Mixer->MixerId = MixerId;
|
|
|
|
ClearMixerCache(Mixer);
|
|
|
|
Ret = SndMixerQueryDestinations(Mixer);
|
|
|
|
if (!Ret)
|
|
{
|
|
ClearMixerCache(Mixer);
|
|
}
|
|
|
|
return Ret;
|
|
}
|
|
else
|
|
{
|
|
mixerClose(Mixer->hmx);
|
|
}
|
|
}
|
|
|
|
Mixer->hmx = NULL;
|
|
Mixer->MixerId = NO_MIXER_SELECTED;
|
|
return FALSE;
|
|
}
|
|
|
|
UINT
|
|
SndMixerGetSelection(PSND_MIXER Mixer)
|
|
{
|
|
return Mixer->MixerId;
|
|
}
|
|
|
|
INT
|
|
SndMixerGetProductName(PSND_MIXER Mixer,
|
|
LPTSTR lpBuffer,
|
|
UINT uSize)
|
|
{
|
|
if (Mixer->hmx)
|
|
{
|
|
UINT lnsz = (UINT) lstrlen(Mixer->Caps.szPname);
|
|
if(lnsz + 1 > uSize)
|
|
{
|
|
return lnsz + 1;
|
|
}
|
|
else
|
|
{
|
|
memcpy(lpBuffer, Mixer->Caps.szPname, lnsz * sizeof(TCHAR));
|
|
lpBuffer[lnsz] = _T('\0');
|
|
return lnsz;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
INT
|
|
SndMixerGetLineName(PSND_MIXER Mixer,
|
|
DWORD LineID,
|
|
LPTSTR lpBuffer,
|
|
UINT uSize,
|
|
BOOL LongName)
|
|
{
|
|
if (Mixer->hmx)
|
|
{
|
|
UINT lnsz;
|
|
PSND_MIXER_DESTINATION Line;
|
|
LPMIXERLINE lpl = NULL;
|
|
|
|
for (Line = Mixer->Lines; Line != NULL; Line = Line->Next)
|
|
{
|
|
if (Line->Info.dwLineID == LineID)
|
|
{
|
|
lpl = &Line->Info;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lpl != NULL)
|
|
{
|
|
lnsz = (UINT) lstrlen(LongName ? lpl->szName : lpl->szShortName);
|
|
if(lnsz + 1 > uSize)
|
|
{
|
|
return lnsz + 1;
|
|
}
|
|
else
|
|
{
|
|
memcpy(lpBuffer, LongName ? lpl->szName : lpl->szShortName, lnsz * sizeof(TCHAR));
|
|
lpBuffer[lnsz] = _T('\0');
|
|
return lnsz;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
BOOL
|
|
SndMixerEnumProducts(PSND_MIXER Mixer,
|
|
PFNSNDMIXENUMPRODUCTS EnumProc,
|
|
PVOID Context)
|
|
{
|
|
MIXERCAPS Caps;
|
|
HMIXER hMixer;
|
|
UINT i;
|
|
BOOL Ret = TRUE;
|
|
|
|
for (i = 0; i < Mixer->MixersCount; i++)
|
|
{
|
|
if (mixerOpen(&hMixer,
|
|
i,
|
|
0,
|
|
0,
|
|
0) == MMSYSERR_NOERROR)
|
|
{
|
|
if (mixerGetDevCaps(i,
|
|
&Caps,
|
|
sizeof(Caps)) == MMSYSERR_NOERROR)
|
|
{
|
|
if (!EnumProc(Mixer,
|
|
i,
|
|
Caps.szPname,
|
|
Context))
|
|
{
|
|
mixerClose(hMixer);
|
|
Ret = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DPRINT("Failed to get device capabilities for mixer id %d!\n", i);
|
|
}
|
|
mixerClose(hMixer);
|
|
}
|
|
}
|
|
|
|
return Ret;
|
|
}
|
|
|
|
INT
|
|
SndMixerSetVolumeControlDetails(PSND_MIXER Mixer, DWORD dwControlID, DWORD cChannels, DWORD cbDetails, LPVOID paDetails)
|
|
{
|
|
MIXERCONTROLDETAILS MixerDetails;
|
|
|
|
if (Mixer->hmx)
|
|
{
|
|
MixerDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
|
|
MixerDetails.dwControlID = dwControlID;
|
|
MixerDetails.cChannels = cChannels;
|
|
MixerDetails.cMultipleItems = 0;
|
|
MixerDetails.cbDetails = cbDetails;
|
|
MixerDetails.paDetails = paDetails;
|
|
|
|
if (mixerSetControlDetails((HMIXEROBJ)Mixer->hmx, &MixerDetails, MIXER_SETCONTROLDETAILSF_VALUE | MIXER_OBJECTF_HMIXER) == MMSYSERR_NOERROR)
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
INT
|
|
SndMixerGetVolumeControlDetails(PSND_MIXER Mixer, DWORD dwControlID, DWORD cChannels, DWORD cbDetails, LPVOID paDetails)
|
|
{
|
|
MIXERCONTROLDETAILS MixerDetails;
|
|
|
|
if (Mixer->hmx)
|
|
{
|
|
MixerDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
|
|
MixerDetails.dwControlID = dwControlID;
|
|
MixerDetails.cChannels = cChannels;
|
|
MixerDetails.cMultipleItems = 0;
|
|
MixerDetails.cbDetails = cbDetails;
|
|
MixerDetails.paDetails = paDetails;
|
|
|
|
if (mixerGetControlDetails((HMIXEROBJ)Mixer->hmx, &MixerDetails, MIXER_GETCONTROLDETAILSF_VALUE | MIXER_OBJECTF_HMIXER) == MMSYSERR_NOERROR)
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
INT
|
|
SndMixerGetDestinationCount(PSND_MIXER Mixer)
|
|
{
|
|
return (Mixer->hmx ? (INT)Mixer->Caps.cDestinations : -1);
|
|
}
|
|
|
|
BOOL
|
|
SndMixerEnumLines(PSND_MIXER Mixer,
|
|
PFNSNDMIXENUMLINES EnumProc,
|
|
PVOID Context)
|
|
{
|
|
if (Mixer->hmx)
|
|
{
|
|
PSND_MIXER_DESTINATION Line;
|
|
|
|
for (Line = Mixer->Lines; Line != NULL; Line = Line->Next)
|
|
{
|
|
if (!EnumProc(Mixer,
|
|
&Line->Info,
|
|
Line->DisplayControls,
|
|
Context))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
SndMixerEnumConnections(PSND_MIXER Mixer,
|
|
DWORD LineID,
|
|
PFNSNDMIXENUMCONNECTIONS EnumProc,
|
|
PVOID Context)
|
|
{
|
|
if (Mixer->hmx)
|
|
{
|
|
PSND_MIXER_DESTINATION Line;
|
|
|
|
for (Line = Mixer->Lines; Line != NULL; Line = Line->Next)
|
|
{
|
|
if (Line->Info.dwLineID == LineID)
|
|
{
|
|
PSND_MIXER_CONNECTION Connection;
|
|
|
|
if (Line->DisplayControls != 0)
|
|
{
|
|
if (!EnumProc(Mixer,
|
|
LineID,
|
|
&Line->Info,
|
|
Context))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
for (Connection = Line->Connections; Connection != NULL; Connection = Connection->Next)
|
|
{
|
|
if (!EnumProc(Mixer,
|
|
LineID,
|
|
&Connection->Info,
|
|
Context))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
SndMixerIsDisplayControl(PSND_MIXER Mixer,
|
|
LPMIXERCONTROL Control)
|
|
{
|
|
if (Mixer->hmx && !(Control->fdwControl & MIXERCONTROL_CONTROLF_DISABLED))
|
|
{
|
|
switch (Control->dwControlType & MIXERCONTROL_CT_CLASS_MASK)
|
|
{
|
|
case MIXERCONTROL_CT_CLASS_FADER:
|
|
case MIXERCONTROL_CT_CLASS_SWITCH:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
LPMIXERLINE
|
|
SndMixerGetLineByName(PSND_MIXER Mixer,
|
|
DWORD LineID,
|
|
LPWSTR LineName)
|
|
{
|
|
PSND_MIXER_DESTINATION Line;
|
|
PSND_MIXER_CONNECTION Connection;
|
|
|
|
if (Mixer->hmx == 0)
|
|
return NULL;
|
|
|
|
for (Line = Mixer->Lines; Line != NULL; Line = Line->Next)
|
|
{
|
|
if (Line->Info.dwLineID == LineID)
|
|
{
|
|
if (Line->DisplayControls != 0)
|
|
{
|
|
if (wcsicmp(Line->Info.szName, LineName) == 0)
|
|
{
|
|
return &Line->Info;
|
|
}
|
|
}
|
|
|
|
for (Connection = Line->Connections; Connection != NULL; Connection = Connection->Next)
|
|
{
|
|
if (wcsicmp(Connection->Info.szName, LineName) == 0)
|
|
{
|
|
return &Connection->Info;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
} |