[MMSYS] Take the balance between channels into account for the master volume trackbar.
[reactos.git] / dll / cpl / mmsys / speakervolume.c
1 /*
2 * PROJECT: ReactOS Multimedia Control Panel
3 * FILE: dll/cpl/mmsys/speakervolume.c
4 * PURPOSE: ReactOS Multimedia Control Panel
5 * PROGRAMMER: Eric Kohl <eric.kohl@reactos.com>
6 */
7
8 #include "mmsys.h"
9
10 typedef struct _PAGE_DATA
11 {
12 HMIXER hMixer;
13 DWORD volumeControlID;
14 DWORD volumeChannels;
15 DWORD volumeMinimum;
16 DWORD volumeMaximum;
17 DWORD volumeStep;
18 PMIXERCONTROLDETAILS_UNSIGNED volumeValues;
19 BOOL volumeSync;
20 } PAGE_DATA, *PPAGE_DATA;
21
22
23 static
24 BOOL
25 OnInitDialog(
26 PPAGE_DATA pPageData,
27 HWND hwndDlg)
28 {
29 TCHAR szBuffer[256];
30 MIXERLINE mxln;
31 MIXERCONTROL mxc;
32 MIXERLINECONTROLS mxlctrl;
33 MIXERCONTROLDETAILS mxcd;
34 INT i, j;
35
36 /* Open the mixer */
37 if (mixerOpen(&pPageData->hMixer, 0, PtrToUlong(hwndDlg), 0, MIXER_OBJECTF_MIXER | CALLBACK_WINDOW) != MMSYSERR_NOERROR)
38 {
39 MessageBox(hwndDlg, _T("Cannot open mixer"), NULL, MB_OK);
40 return FALSE;
41 }
42
43 /* Retrieve the mixer information */
44 mxln.cbStruct = sizeof(MIXERLINE);
45 mxln.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
46
47 if (mixerGetLineInfo((HMIXEROBJ)pPageData->hMixer, &mxln, MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE) != MMSYSERR_NOERROR)
48 return FALSE;
49
50 pPageData->volumeChannels = mxln.cChannels;
51
52 /* Retrieve the line information */
53 mxlctrl.cbStruct = sizeof(MIXERLINECONTROLS);
54 mxlctrl.dwLineID = mxln.dwLineID;
55 mxlctrl.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
56 mxlctrl.cControls = 1;
57 mxlctrl.cbmxctrl = sizeof(MIXERCONTROL);
58 mxlctrl.pamxctrl = &mxc;
59
60 if (mixerGetLineControls((HMIXEROBJ)pPageData->hMixer, &mxlctrl, MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE) != MMSYSERR_NOERROR)
61 return FALSE;
62
63 pPageData->volumeControlID = mxc.dwControlID;
64 pPageData->volumeMinimum = mxc.Bounds.dwMinimum;
65 pPageData->volumeMaximum = mxc.Bounds.dwMaximum;
66 pPageData->volumeStep = (pPageData->volumeMaximum - pPageData->volumeMinimum) / (VOLUME_MAX - VOLUME_MIN);
67
68 /* Allocate a buffer for all channel volume values */
69 pPageData->volumeValues = HeapAlloc(GetProcessHeap(),
70 0,
71 mxln.cChannels * sizeof(MIXERCONTROLDETAILS_UNSIGNED));
72 if (pPageData->volumeValues == NULL)
73 return FALSE;
74
75 /* Retrieve the channel volume values */
76 mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
77 mxcd.dwControlID = mxc.dwControlID;
78 mxcd.cChannels = mxln.cChannels;
79 mxcd.cMultipleItems = 0;
80 mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
81 mxcd.paDetails = pPageData->volumeValues;
82
83 if (mixerGetControlDetails((HMIXEROBJ)pPageData->hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR)
84 return FALSE;
85
86 /* Initialize the channels */
87 for (i = 0; i < min(mxln.cChannels, 5); i++)
88 {
89 j = i * 4;
90
91 /* Set the channel name */
92 LoadString(hApplet, IDS_SPEAKER_LEFT + i, szBuffer, _countof(szBuffer));
93 SetWindowText(GetDlgItem(hwndDlg, 9472 + j), szBuffer);
94
95 /* Initialize the channel trackbar */
96 SendDlgItemMessage(hwndDlg, 9475 + j, TBM_SETRANGE, (WPARAM)TRUE, (LPARAM)MAKELONG(VOLUME_MIN, VOLUME_MAX));
97 SendDlgItemMessage(hwndDlg, 9475 + j, TBM_SETTICFREQ, VOLUME_TICFREQ, 0);
98 SendDlgItemMessage(hwndDlg, 9475 + j, TBM_SETPAGESIZE, 0, VOLUME_PAGESIZE);
99 SendDlgItemMessage(hwndDlg, 9475 + j, TBM_SETPOS, (WPARAM)TRUE, (LPARAM)(pPageData->volumeValues[i].dwValue - pPageData->volumeMinimum) / pPageData->volumeStep);
100 }
101
102 /* Hide the unused controls */
103 for (i = mxln.cChannels; i < 8; i++)
104 {
105 j = i * 4;
106 ShowWindow(GetDlgItem(hwndDlg, 9472 + j), SW_HIDE);
107 ShowWindow(GetDlgItem(hwndDlg, 9473 + j), SW_HIDE);
108 ShowWindow(GetDlgItem(hwndDlg, 9474 + j), SW_HIDE);
109 ShowWindow(GetDlgItem(hwndDlg, 9475 + j), SW_HIDE);
110 }
111
112 return TRUE;
113 }
114
115
116 static
117 VOID
118 OnMixerControlChange(
119 PPAGE_DATA pPageData,
120 HWND hwndDlg)
121 {
122 MIXERCONTROLDETAILS mxcd;
123 INT i, j;
124
125 /* Retrieve the channel volume values */
126 mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
127 mxcd.dwControlID = pPageData->volumeControlID;
128 mxcd.cChannels = pPageData->volumeChannels;
129 mxcd.cMultipleItems = 0;
130 mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
131 mxcd.paDetails = pPageData->volumeValues;
132
133 if (mixerGetControlDetails((HMIXEROBJ)pPageData->hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_GETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR)
134 return;
135
136 for (i = 0; i < pPageData->volumeChannels; i++)
137 {
138 j = i * 4;
139
140 SendDlgItemMessage(hwndDlg, 9475 + j, TBM_SETPOS, (WPARAM)TRUE, (LPARAM)(pPageData->volumeValues[i].dwValue - pPageData->volumeMinimum) / pPageData->volumeStep);
141 }
142 }
143
144
145 static
146 VOID
147 OnHScroll(
148 PPAGE_DATA pPageData,
149 HWND hwndDlg,
150 WPARAM wParam,
151 LPARAM lParam)
152 {
153 MIXERCONTROLDETAILS mxcd;
154 DWORD dwValue, dwPosition;
155 INT id, idx, i, j;
156
157 id = (INT)GetWindowLongPtr((HWND)lParam, GWLP_ID);
158 if (id < 9475 && id > 9503)
159 return;
160
161 if ((id - 9475) % 4 != 0)
162 return;
163
164 dwPosition = (DWORD)SendDlgItemMessage(hwndDlg, id, TBM_GETPOS, 0, 0);
165
166 if (dwPosition == VOLUME_MIN)
167 dwValue = pPageData->volumeMinimum;
168 else if (dwPosition == VOLUME_MAX)
169 dwValue = pPageData->volumeMaximum;
170 else
171 dwValue = (dwPosition * pPageData->volumeStep) + pPageData->volumeMinimum;
172
173 if (pPageData->volumeSync)
174 {
175 for (i = 0; i < pPageData->volumeChannels; i++)
176 {
177 j = 9475 + (i * 4);
178 if (j != id)
179 SendDlgItemMessage(hwndDlg, j, TBM_SETPOS, (WPARAM)TRUE, dwPosition);
180
181 pPageData->volumeValues[i].dwValue = dwValue;
182 }
183 }
184 else
185 {
186 idx = (id - 9475) / 4;
187 pPageData->volumeValues[idx].dwValue = dwValue;
188 }
189
190 mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
191 mxcd.dwControlID = pPageData->volumeControlID;
192 mxcd.cChannels = pPageData->volumeChannels;
193 mxcd.cMultipleItems = 0;
194 mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
195 mxcd.paDetails = pPageData->volumeValues;
196
197 if (mixerSetControlDetails((HMIXEROBJ)pPageData->hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR)
198 return;
199 }
200
201
202 static
203 VOID
204 OnSetDefaults(
205 PPAGE_DATA pPageData,
206 HWND hwndDlg)
207 {
208 MIXERCONTROLDETAILS mxcd;
209 DWORD dwValue, i;
210
211 dwValue = ((VOLUME_MAX - VOLUME_MIN) / 2 * pPageData->volumeStep) + pPageData->volumeMinimum;
212
213 for (i = 0; i < pPageData->volumeChannels; i++)
214 pPageData->volumeValues[i].dwValue = dwValue;
215
216 mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
217 mxcd.dwControlID = pPageData->volumeControlID;
218 mxcd.cChannels = pPageData->volumeChannels;
219 mxcd.cMultipleItems = 0;
220 mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
221 mxcd.paDetails = pPageData->volumeValues;
222
223 if (mixerSetControlDetails((HMIXEROBJ)pPageData->hMixer, &mxcd, MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE) != MMSYSERR_NOERROR)
224 return;
225 }
226
227
228 INT_PTR
229 CALLBACK
230 SpeakerVolumeDlgProc(
231 HWND hwndDlg,
232 UINT uMsg,
233 WPARAM wParam,
234 LPARAM lParam)
235 {
236 PPAGE_DATA pPageData;
237
238 UNREFERENCED_PARAMETER(wParam);
239
240 pPageData = (PPAGE_DATA)GetWindowLongPtr(hwndDlg, DWLP_USER);
241
242 switch(uMsg)
243 {
244 case WM_INITDIALOG:
245 pPageData = (PPAGE_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PAGE_DATA));
246 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pPageData);
247
248 if (pPageData)
249 {
250 OnInitDialog(pPageData, hwndDlg);
251 }
252 break;
253
254 case WM_DESTROY:
255 if (pPageData)
256 {
257 if (pPageData->volumeValues)
258 HeapFree(GetProcessHeap(), 0, pPageData->volumeValues);
259
260 if (pPageData->hMixer)
261 mixerClose(pPageData->hMixer);
262
263 HeapFree(GetProcessHeap(), 0, pPageData);
264 pPageData = NULL;
265 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)NULL);
266 }
267 break;
268
269 case WM_HSCROLL:
270 if (pPageData)
271 {
272 OnHScroll(pPageData, hwndDlg, wParam, lParam);
273 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
274 }
275 break;
276
277 case WM_COMMAND:
278 switch (LOWORD(wParam))
279 {
280 case 9504:
281 if (HIWORD(wParam) == BN_CLICKED)
282 pPageData->volumeSync = (SendDlgItemMessage(hwndDlg, 9504, BM_GETCHECK, 0, 0) == BST_CHECKED);
283 break;
284
285 case 9505:
286 OnSetDefaults(pPageData, hwndDlg);
287 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
288 break;
289 }
290 break;
291
292 case WM_NOTIFY:
293 return TRUE;
294
295 case MM_MIXM_CONTROL_CHANGE:
296 if (pPageData)
297 OnMixerControlChange(pPageData, hwndDlg);
298 break;
299 }
300
301 return FALSE;
302 }
303
304
305 INT_PTR
306 SpeakerVolume(
307 HWND hwndDlg)
308 {
309 PROPSHEETPAGE psp[1];
310 PROPSHEETHEADER psh;
311 TCHAR Caption[256];
312
313 LoadString(hApplet, IDS_SPEAKER_VOLUME, Caption, _countof(Caption));
314
315 ZeroMemory(&psh, sizeof(PROPSHEETHEADER));
316 psh.dwSize = sizeof(PROPSHEETHEADER);
317 psh.dwFlags = PSH_PROPSHEETPAGE;
318 psh.hwndParent = hwndDlg;
319 psh.hInstance = hApplet;
320 psh.pszCaption = Caption;
321 psh.nPages = sizeof(psp) / sizeof(PROPSHEETPAGE);
322 psh.nStartPage = 0;
323 psh.ppsp = psp;
324
325 InitPropSheetPage(&psp[0], IDD_MULTICHANNEL, SpeakerVolumeDlgProc);
326 psp[0].dwFlags |= PSP_USETITLE;
327 psp[0].pszTitle = Caption;
328
329 return (LONG)(PropertySheet(&psh) != -1);
330 }