msi: Generalize media helpers a bit.
[wine/multimedia.git] / dlls / winealsa.drv / mixer.c
blob833d2aef3d897651e258861891f3dc03b151fcde
1 /*
2 * Alsa MIXER Wine Driver for Linux
3 * Very loosely based on wineoss mixer driver
5 * Copyright 2007 Maarten Lankhorst
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #include "config.h"
23 #include "wine/port.h"
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <string.h>
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32 #include <fcntl.h>
33 #include <errno.h>
34 #include <assert.h>
35 #ifdef HAVE_SYS_IOCTL_H
36 # include <sys/ioctl.h>
37 #endif
39 #define NONAMELESSUNION
40 #define NONAMELESSSTRUCT
42 #include "windef.h"
43 #include "winbase.h"
44 #include "wingdi.h"
45 #include "winuser.h"
46 #include "winnls.h"
47 #include "mmddk.h"
48 #include "mmsystem.h"
49 #include "alsa.h"
50 #include "wine/unicode.h"
51 #include "wine/debug.h"
53 WINE_DEFAULT_DEBUG_CHANNEL(mixer);
55 #define WINE_MIXER_MANUF_ID 0xAA
56 #define WINE_MIXER_PRODUCT_ID 0x55
57 #define WINE_MIXER_VERSION 0x0100
59 /* Generic notes:
60 * In windows it seems to be required for all controls to have a volume switch
61 * In alsa that's optional
63 * I assume for playback controls, that there is always a playback volume switch available
64 * Mute is optional
66 * For capture controls, it is needed that there is a capture switch and a volume switch,
67 * It doesn't matter whether it is a playback volume switch or a capture volume switch.
68 * The code will first try to get/adjust capture volume, if that fails it tries playback volume
69 * It is not pretty, but under my 3 test cards it seems that there is no other choice:
70 * Most capture controls don't have a capture volume setting
72 * MUX means that only capture source can be exclusively selected,
73 * MIXER means that multiple sources can be selected simultaneously.
76 static const char * getMessage(UINT uMsg)
78 #define MSG_TO_STR(x) case x: return #x;
79 switch (uMsg){
80 MSG_TO_STR(DRVM_INIT);
81 MSG_TO_STR(DRVM_EXIT);
82 MSG_TO_STR(DRVM_ENABLE);
83 MSG_TO_STR(DRVM_DISABLE);
84 MSG_TO_STR(MXDM_GETDEVCAPS);
85 MSG_TO_STR(MXDM_GETLINEINFO);
86 MSG_TO_STR(MXDM_GETNUMDEVS);
87 MSG_TO_STR(MXDM_OPEN);
88 MSG_TO_STR(MXDM_CLOSE);
89 MSG_TO_STR(MXDM_GETLINECONTROLS);
90 MSG_TO_STR(MXDM_GETCONTROLDETAILS);
91 MSG_TO_STR(MXDM_SETCONTROLDETAILS);
92 default: break;
94 #undef MSG_TO_STR
95 return wine_dbg_sprintf("UNKNOWN(%08x)", uMsg);
98 static const char * getControlType(DWORD dwControlType)
100 #define TYPE_TO_STR(x) case x: return #x;
101 switch (dwControlType) {
102 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM);
103 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER);
104 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER);
105 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER);
106 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER);
107 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN);
108 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF);
109 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE);
110 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO);
111 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS);
112 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH);
113 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST);
114 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON);
115 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS);
116 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED);
117 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED);
118 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT);
119 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER);
120 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN);
121 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN);
122 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER);
123 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME);
124 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS);
125 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE);
126 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER);
127 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT);
128 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX);
129 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT);
130 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER);
131 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME);
132 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME);
134 #undef TYPE_TO_STR
135 return wine_dbg_sprintf("UNKNOWN(%08x)", dwControlType);
138 /* A simple declaration of a line control
139 * These are each of the channels that show up
141 typedef struct line {
142 /* Name we present to outside world */
143 WCHAR name[MAXPNAMELEN];
145 DWORD component;
146 DWORD dst;
147 DWORD capt;
148 DWORD chans;
149 snd_mixer_elem_t *elem;
150 } line;
152 /* A control structure, with toggle enabled switch
153 * Control structures control volume, muted, which capture source
155 typedef struct control {
156 BOOL enabled;
157 MIXERCONTROLW c;
158 } control;
160 /* Mixer device */
161 typedef struct mixer
163 snd_mixer_t *mix;
164 WCHAR mixername[MAXPNAMELEN];
166 int chans, dests;
167 LPDRVCALLBACK callback;
168 DWORD_PTR callbackpriv;
169 HDRVR hmx;
171 line *lines;
172 control *controls;
173 } mixer;
175 #define MAX_MIXERS 32
176 #define CONTROLSPERLINE 3
177 #define OFS_MUTE 2
178 #define OFS_MUX 1
180 static int cards = 0;
181 static mixer mixdev[MAX_MIXERS];
182 static HANDLE thread;
183 static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask);
184 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam);
185 static CRITICAL_SECTION elem_crst;
186 static int msg_pipe[2];
187 static LONG refcnt;
189 /* found channel names in alsa lib, alsa api doesn't have another way for this
190 * map name -> componenttype, worst case we get a wrong componenttype which is
191 * mostly harmless
194 static const struct mixerlinetype {
195 const char *name; DWORD cmpt;
196 } converttable[] = {
197 { "Master", MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, },
198 { "Capture", MIXERLINE_COMPONENTTYPE_DST_WAVEIN, },
199 { "PCM", MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT, },
200 { "PC Speaker", MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER, },
201 { "Synth", MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER, },
202 { "Headphone", MIXERLINE_COMPONENTTYPE_DST_HEADPHONES, },
203 { "Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
204 { "Aux", MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED, },
205 { "CD", MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC, },
206 { "Line", MIXERLINE_COMPONENTTYPE_SRC_LINE, },
207 { "Phone", MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE, },
208 { "Digital", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
209 { "Front Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
212 /* Map name to MIXERLINE_COMPONENTTYPE_XXX */
213 static int getcomponenttype(const char *name)
215 int x;
216 for (x=0; x< sizeof(converttable)/sizeof(converttable[0]); ++x)
217 if (!strcasecmp(name, converttable[x].name))
219 TRACE("%d -> %s\n", x, name);
220 return converttable[x].cmpt;
222 WARN("Unknown mixer name %s, probably harmless\n", name);
223 return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
226 /* Is this control suited for showing up? */
227 static int blacklisted(snd_mixer_elem_t *elem)
229 const char *name = snd_mixer_selem_get_name(elem);
230 BOOL blisted = 0;
232 if (!snd_mixer_selem_has_playback_volume(elem) &&
233 !snd_mixer_selem_has_capture_volume(elem))
234 blisted = 1;
236 TRACE("%s: %x\n", name, blisted);
237 return blisted;
240 static void fillcontrols(mixer *mmixer)
242 int id;
243 for (id = 0; id < mmixer->chans; ++id)
245 line *mline = &mmixer->lines[id];
246 int ofs = CONTROLSPERLINE * id;
247 int x;
248 long min, max;
250 TRACE("Filling control %d\n", id);
251 if (!mline->elem)
252 break;
253 if (id == 1 && !mline->elem)
254 continue;
256 if (mline->capt && snd_mixer_selem_has_capture_volume(mline->elem))
257 snd_mixer_selem_get_capture_volume_range(mline->elem, &min, &max);
258 else
259 snd_mixer_selem_get_playback_volume_range(mline->elem, &min, &max);
261 /* (!snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) */
262 /* Volume, always enabled by definition of blacklisted channels */
263 mmixer->controls[ofs].enabled = 1;
264 mmixer->controls[ofs].c.cbStruct = sizeof(mmixer->controls[ofs].c);
265 mmixer->controls[ofs].c.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
266 mmixer->controls[ofs].c.dwControlID = ofs;
267 mmixer->controls[ofs].c.Bounds.s1.dwMinimum = 0;
268 mmixer->controls[ofs].c.Bounds.s1.dwMaximum = 65535;
269 mmixer->controls[ofs].c.Metrics.cSteps = 65536/(max-min);
271 if ((id == 1 && snd_mixer_selem_has_capture_switch(mline->elem)) ||
272 (!mline->capt && snd_mixer_selem_has_playback_switch(mline->elem)))
273 { /* MUTE button optional, main capture channel should have one too */
274 mmixer->controls[ofs+OFS_MUTE].enabled = 1;
275 mmixer->controls[ofs+OFS_MUTE].c.cbStruct = sizeof(mmixer->controls[ofs].c);
276 mmixer->controls[ofs+OFS_MUTE].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
277 mmixer->controls[ofs+OFS_MUTE].c.dwControlID = ofs+OFS_MUTE;
278 mmixer->controls[ofs+OFS_MUTE].c.Bounds.s1.dwMaximum = 1;
281 if (mline->capt && snd_mixer_selem_has_capture_switch_exclusive(mline->elem))
282 mmixer->controls[CONTROLSPERLINE+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUX;
284 if (id == 1)
285 { /* Capture select, in case cMultipleItems is 0, it means capture is disabled anyway */
286 mmixer->controls[ofs+OFS_MUX].enabled = 1;
287 mmixer->controls[ofs+OFS_MUX].c.cbStruct = sizeof(mmixer->controls[ofs].c);
288 mmixer->controls[ofs+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MIXER;
289 mmixer->controls[ofs+OFS_MUX].c.dwControlID = ofs+OFS_MUX;
290 mmixer->controls[ofs+OFS_MUX].c.fdwControl = MIXERCONTROL_CONTROLF_MULTIPLE;
292 for (x = 0; x<mmixer->chans; ++x)
293 if (x != id && mmixer->lines[x].dst == id)
294 ++(mmixer->controls[ofs+OFS_MUX].c.cMultipleItems);
295 if (!mmixer->controls[ofs+OFS_MUX].c.cMultipleItems)
296 mmixer->controls[ofs+OFS_MUX].enabled = 0;
298 mmixer->controls[ofs+OFS_MUX].c.Bounds.s1.dwMaximum = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems - 1;
299 mmixer->controls[ofs+OFS_MUX].c.Metrics.cSteps = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems;
301 for (x=0; x<CONTROLSPERLINE; ++x)
303 lstrcpynW(mmixer->controls[ofs+x].c.szShortName, mline->name, sizeof(mmixer->controls[ofs+x].c.szShortName)/sizeof(WCHAR));
304 lstrcpynW(mmixer->controls[ofs+x].c.szName, mline->name, sizeof(mmixer->controls[ofs+x].c.szName)/sizeof(WCHAR));
309 /* get amount of channels for elem */
310 /* Officially we should keep capture/playback separated,
311 * but that's not going to work in the alsa api */
312 static int chans(mixer *mmixer, snd_mixer_elem_t * elem, DWORD capt)
314 int ret=0, chn;
316 if (capt && snd_mixer_selem_has_capture_volume(elem)) {
317 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
318 if (snd_mixer_selem_has_capture_channel(elem, chn))
319 ++ret;
320 } else {
321 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
322 if (snd_mixer_selem_has_playback_channel(elem, chn))
323 ++ret;
325 if (!ret)
326 FIXME("Mixer channel %s was found for %s, but no channels were found? Wrong selection!\n", snd_mixer_selem_get_name(elem), (snd_mixer_selem_has_playback_volume(elem) ? "playback" : "capture"));
327 return ret;
330 static void filllines(mixer *mmixer, snd_mixer_elem_t *mastelem, snd_mixer_elem_t *captelem, int capt)
332 snd_mixer_elem_t *elem;
333 line *mline = mmixer->lines;
335 if (mastelem) {
336 /* Master control */
337 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(mastelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
338 mline->component = getcomponenttype(snd_mixer_selem_get_name(mastelem));
339 mline->dst = 0;
340 mline->capt = 0;
341 mline->elem = mastelem;
342 mline->chans = chans(mmixer, mastelem, 0);
344 snd_mixer_elem_set_callback(mastelem, &elem_callback);
345 snd_mixer_elem_set_callback_private(mastelem, mmixer);
346 } else {
347 MultiByteToWideChar(CP_UNIXCP, 0, "Empty Master Element", -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
350 /* Capture control
351 * Note: since mmixer->dests = 1, it means only playback control is visible
352 * This makes sense, because if there are no capture sources capture control
353 * can't do anything and should be invisible */
355 /* Control 1 is reserved for capture even when not enabled */
356 ++mline;
357 if (capt)
359 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
360 mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem));
361 mline->dst = 1;
362 mline->capt = 1;
363 mline->elem = captelem;
364 mline->chans = chans(mmixer, captelem, 1);
366 snd_mixer_elem_set_callback(captelem, &elem_callback);
367 snd_mixer_elem_set_callback_private(captelem, mmixer);
370 for (elem = snd_mixer_first_elem(mmixer->mix); elem; elem = snd_mixer_elem_next(elem))
371 if (elem != mastelem && elem != captelem && !blacklisted(elem))
373 const char * name = snd_mixer_selem_get_name(elem);
374 DWORD comp = getcomponenttype(name);
376 if (snd_mixer_selem_has_playback_volume(elem) &&
377 (snd_mixer_selem_has_capture_volume(elem) || comp != MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE))
379 (++mline)->component = comp;
380 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
381 mline->capt = mline->dst = 0;
382 mline->elem = elem;
383 mline->chans = chans(mmixer, elem, 0);
385 else if (!capt)
386 continue;
388 if (capt && (snd_mixer_selem_has_capture_volume(elem) || comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE))
390 (++mline)->component = comp;
391 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
392 mline->capt = mline->dst = 1;
393 mline->elem = elem;
394 mline->chans = chans(mmixer, elem, 1);
397 snd_mixer_elem_set_callback(elem, &elem_callback);
398 snd_mixer_elem_set_callback_private(elem, mmixer);
402 static void filllines_no_master(mixer *mmixer, snd_mixer_elem_t *captelem, int capt)
404 line *mline = mmixer->lines;
406 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
407 mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem));
408 mline->dst = 0;
409 mline->capt = 1;
410 mline->elem = captelem;
411 mline->chans = chans(mmixer, captelem, 1);
413 snd_mixer_elem_set_callback(captelem, &elem_callback);
414 snd_mixer_elem_set_callback_private(captelem, mmixer);
417 /* Windows api wants to have a 'master' device to which all slaves are attached
418 * There are 2 ones in this code:
419 * - 'Master', fall back to 'Headphone' if unavailable, and if that's not available 'PCM'
420 * - 'Capture'
421 * Capture might not always be available, so should be prepared to be without if needed
424 static void ALSA_MixerInit(void)
426 int x, mixnum = 0;
427 snd_ctl_card_info_t *info;
429 info = HeapAlloc( GetProcessHeap(), 0, snd_ctl_card_info_sizeof());
430 for (x = 0; x < MAX_MIXERS; ++x)
432 int card, err, capcontrols = 0, total_elems = 0;
433 char cardind[6], cardname[10];
435 snd_ctl_t *ctl;
436 snd_mixer_elem_t *elem, *mastelem = NULL, *headelem = NULL, *captelem = NULL, *pcmelem = NULL, *lineelem = NULL, *micelem = NULL;
438 memset(info, 0, snd_ctl_card_info_sizeof());
439 memset(&mixdev[mixnum], 0, sizeof(*mixdev));
440 snprintf(cardind, sizeof(cardind), "%d", x);
441 card = snd_card_get_index(cardind);
442 if (card < 0)
443 continue;
445 snprintf(cardname, sizeof(cardname), "hw:%d", card);
447 err = snd_ctl_open(&ctl, cardname, 0);
448 if (err < 0)
450 WARN("Cannot open card: %s\n", snd_strerror(err));
451 continue;
454 err = snd_ctl_card_info(ctl, info);
455 if (err < 0)
457 WARN("Cannot get card info: %s\n", snd_strerror(err));
458 snd_ctl_close(ctl);
459 continue;
462 MultiByteToWideChar(CP_UNIXCP, 0, snd_ctl_card_info_get_name(info), -1, mixdev[mixnum].mixername, sizeof(mixdev[mixnum].mixername)/sizeof(WCHAR));
463 snd_ctl_close(ctl);
465 err = snd_mixer_open(&mixdev[mixnum].mix, 0);
466 if (err < 0)
468 WARN("Error occurred opening mixer: %s\n", snd_strerror(err));
469 continue;
472 err = snd_mixer_attach(mixdev[mixnum].mix, cardname);
473 if (err < 0)
474 goto eclose;
476 err = snd_mixer_selem_register(mixdev[mixnum].mix, NULL, NULL);
477 if (err < 0)
478 goto eclose;
480 err = snd_mixer_load(mixdev[mixnum].mix);
481 if (err < 0)
482 goto eclose;
484 /* First, lets see what's available..
485 * If there are multiple Master or Captures, all except 1 will be added as slaves
487 total_elems = snd_mixer_get_count(mixdev[mixnum].mix);
488 TRACE("Total elems: %d\n", total_elems);
490 for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
491 if (!strcasecmp(snd_mixer_selem_get_name(elem), "Master") && !mastelem)
493 mastelem = elem;
494 ++(mixdev[mixnum].chans);
496 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Capture") && !captelem)
497 captelem = elem;
498 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Mic") && !micelem && !mastelem && total_elems == 1)
499 /* this is what snd-usb-audio mics look like; just a Mic control and that's it.*/
500 micelem = elem;
501 else if (!blacklisted(elem))
503 DWORD comp = getcomponenttype(snd_mixer_selem_get_name(elem));
504 DWORD skip = 0;
506 /* Work around buggy drivers: Make this a capture control if the name is recognised as a microphone */
507 if (snd_mixer_selem_has_capture_volume(elem))
508 ++capcontrols;
509 else if (comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)
511 ++capcontrols;
512 skip = 1;
515 if (!skip && snd_mixer_selem_has_playback_volume(elem))
517 if (!strcasecmp(snd_mixer_selem_get_name(elem), "Headphone") && !headelem)
518 headelem = elem;
519 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "PCM") && !pcmelem)
520 pcmelem = elem;
521 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Line") && !lineelem)
522 lineelem = elem;
523 ++(mixdev[mixnum].chans);
527 /* Add dummy capture channel, wanted by Windows */
528 mixdev[mixnum].chans += 1;
530 /* If there is only 'Capture' and 'Master', this device is not worth it */
531 if (mixdev[mixnum].chans == 2)
533 WARN("No channels found, skipping device!\n");
534 goto close;
537 /* Master element can't have a capture control in this code, so
538 * if Headphone or PCM is promoted to master, unset its capture control */
539 if (headelem && !mastelem)
541 /* Using 'Headphone' as master device */
542 mastelem = headelem;
543 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
545 else if (pcmelem && !mastelem)
547 /* Use 'PCM' as master device */
548 mastelem = pcmelem;
549 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
551 else if (lineelem && !mastelem)
553 /* Use 'Line' as master device */
554 mastelem = lineelem;
555 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
557 else if (!mastelem && !captelem && !micelem)
559 /* If there is nothing sensible that can act as 'Master' control, something is wrong */
560 FIXME("No master control found on %s, disabling mixer\n", snd_ctl_card_info_get_name(info));
561 goto close;
564 if (!captelem || !capcontrols)
566 /* Can't enable capture, so disabling it
567 * Note: capture control will still exist because
568 * dwLineID 0 and 1 are reserved for Master and Capture
570 WARN("No use enabling capture part of mixer, capture control found: %s, amount of capture controls: %d\n",
571 (!captelem ? "no" : "yes"), capcontrols);
572 capcontrols = 0;
573 mixdev[mixnum].dests = 1;
575 else
577 mixdev[mixnum].chans += capcontrols;
578 mixdev[mixnum].dests = 2;
581 mixdev[mixnum].lines = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(line) * mixdev[mixnum].chans);
582 mixdev[mixnum].controls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(control) * CONTROLSPERLINE*mixdev[mixnum].chans);
583 err = -ENOMEM;
584 if (!mixdev[mixnum].lines || !mixdev[mixnum].controls)
585 goto close;
587 if (mastelem)
588 filllines(&mixdev[mixnum], mastelem, captelem, capcontrols);
589 else if (micelem)
590 filllines_no_master(&mixdev[mixnum], micelem, 1);
591 fillcontrols(&mixdev[mixnum]);
593 TRACE("%s: Amount of controls: %i/%i, name: %s\n", cardname, mixdev[mixnum].dests, mixdev[mixnum].chans, debugstr_w(mixdev[mixnum].mixername));
594 mixnum++;
595 continue;
597 eclose:
598 WARN("Error occurred initialising mixer: %s\n", snd_strerror(err));
599 close:
600 HeapFree(GetProcessHeap(), 0, mixdev[mixnum].lines);
601 HeapFree(GetProcessHeap(), 0, mixdev[mixnum].controls);
602 snd_mixer_close(mixdev[mixnum].mix);
604 cards = mixnum;
605 HeapFree( GetProcessHeap(), 0, info );
607 /* There is no trouble with already assigning callbacks without initialising critsect:
608 * Callbacks only occur when snd_mixer_handle_events is called (only happens in thread)
610 InitializeCriticalSection(&elem_crst);
611 elem_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_MIXER.elem_crst");
612 TRACE("\n");
615 static void ALSA_MixerExit(void)
617 int x;
619 if (refcnt)
621 WARN("Callback thread still alive, terminating uncleanly, refcnt: %d\n", refcnt);
622 /* Least we can do is making sure we're not in 'foreign' code */
623 EnterCriticalSection(&elem_crst);
624 TerminateThread(thread, 1);
625 refcnt = 0;
626 LeaveCriticalSection(&elem_crst);
629 TRACE("Cleaning up\n");
631 elem_crst.DebugInfo->Spare[0] = 0;
632 DeleteCriticalSection(&elem_crst);
633 for (x = 0; x < cards; ++x)
635 snd_mixer_close(mixdev[x].mix);
636 HeapFree(GetProcessHeap(), 0, mixdev[x].lines);
637 HeapFree(GetProcessHeap(), 0, mixdev[x].controls);
639 cards = 0;
642 static mixer* MIX_GetMix(UINT wDevID)
644 mixer *mmixer;
646 if (wDevID >= cards)
648 WARN("Invalid mixer id: %d\n", wDevID);
649 return NULL;
652 mmixer = &mixdev[wDevID];
653 return mmixer;
656 /* Since alsa doesn't tell what exactly changed, just assume all affected controls changed */
657 static int elem_callback(snd_mixer_elem_t *elem, unsigned int type)
659 mixer *mmixer = snd_mixer_elem_get_callback_private(elem);
660 int x;
661 BOOL captchanged = 0;
663 if (type != SND_CTL_EVENT_MASK_VALUE)
664 return 0;
666 assert(mmixer);
668 EnterCriticalSection(&elem_crst);
670 if (!mmixer->callback)
671 goto out;
673 for (x=0; x<mmixer->chans; ++x)
675 const int ofs = CONTROLSPERLINE*x;
676 if (elem != mmixer->lines[x].elem)
677 continue;
679 if (mmixer->lines[x].capt)
680 ++captchanged;
682 TRACE("Found changed control %s\n", debugstr_w(mmixer->lines[x].name));
683 mmixer->callback(mmixer->hmx, MM_MIXM_LINE_CHANGE, mmixer->callbackpriv, x, 0);
684 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs, 0);
686 if (mmixer->controls[ofs+OFS_MUTE].enabled)
687 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs+OFS_MUTE, 0);
689 if (captchanged)
690 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, CONTROLSPERLINE+OFS_MUX, 0);
692 out:
693 LeaveCriticalSection(&elem_crst);
695 return 0;
698 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam)
700 struct pollfd *pfds = NULL;
701 int x, y, err, mcnt, count = 1;
703 TRACE("%p\n", lParam);
705 for (x = 0; x < cards; ++x)
706 count += snd_mixer_poll_descriptors_count(mixdev[x].mix);
708 TRACE("Counted %d descriptors\n", count);
709 pfds = HeapAlloc(GetProcessHeap(), 0, count * sizeof(struct pollfd));
711 if (!pfds)
713 WARN("Out of memory\n");
714 goto die;
717 pfds[0].fd = msg_pipe[0];
718 pfds[0].events = POLLIN;
720 y = 1;
721 for (x = 0; x < cards; ++x)
722 y += snd_mixer_poll_descriptors(mixdev[x].mix, &pfds[y], count - y);
724 while ((err = poll(pfds, (unsigned int) count, -1)) >= 0 || errno == EINTR || errno == EAGAIN)
726 if (pfds[0].revents & POLLIN)
727 break;
729 mcnt = 1;
730 for (x = y = 0; x < cards; ++x)
732 int j, max = snd_mixer_poll_descriptors_count(mixdev[x].mix);
733 for (j = 0; j < max; ++j)
734 if (pfds[mcnt+j].revents)
736 y += snd_mixer_handle_events(mixdev[x].mix);
737 break;
739 mcnt += max;
741 if (y)
742 TRACE("Handled %d events\n", y);
745 die:
746 TRACE("Shutting down\n");
747 HeapFree(GetProcessHeap(), 0, pfds);
749 y = read(msg_pipe[0], &x, sizeof(x));
750 close(msg_pipe[1]);
751 close(msg_pipe[0]);
752 return 0;
755 static DWORD MIX_Open(UINT wDevID, LPMIXEROPENDESC desc, DWORD_PTR flags)
757 mixer *mmixer = MIX_GetMix(wDevID);
758 if (!mmixer)
759 return MMSYSERR_BADDEVICEID;
761 flags &= CALLBACK_TYPEMASK;
762 switch (flags)
764 case CALLBACK_NULL:
765 goto done;
767 case CALLBACK_FUNCTION:
768 break;
770 default:
771 FIXME("Unhandled callback type: %08lx\n", flags & CALLBACK_TYPEMASK);
772 return MIXERR_INVALVALUE;
775 mmixer->callback = (LPDRVCALLBACK)desc->dwCallback;
776 mmixer->callbackpriv = desc->dwInstance;
777 mmixer->hmx = (HDRVR)desc->hmx;
779 done:
780 if (InterlockedIncrement(&refcnt) == 1)
782 if (pipe(msg_pipe) >= 0)
784 thread = CreateThread(NULL, 0, ALSA_MixerPollThread, NULL, 0, NULL);
785 if (!thread)
787 close(msg_pipe[0]);
788 close(msg_pipe[1]);
789 msg_pipe[0] = msg_pipe[1] = -1;
792 else
793 msg_pipe[0] = msg_pipe[1] = -1;
796 return MMSYSERR_NOERROR;
799 static DWORD MIX_Close(UINT wDevID)
801 int x = 0;
802 mixer *mmixer = MIX_GetMix(wDevID);
803 if (!mmixer)
804 return MMSYSERR_BADDEVICEID;
806 EnterCriticalSection(&elem_crst);
807 mmixer->callback = 0;
808 LeaveCriticalSection(&elem_crst);
810 if (!InterlockedDecrement(&refcnt))
812 if (write(msg_pipe[1], &x, sizeof(x)) > 0)
814 TRACE("Shutting down thread...\n");
815 WaitForSingleObject(thread, INFINITE);
816 TRACE("Done\n");
820 return MMSYSERR_NOERROR;
823 static DWORD MIX_GetDevCaps(UINT wDevID, LPMIXERCAPS2W caps, DWORD_PTR parm2)
825 mixer *mmixer = MIX_GetMix(wDevID);
826 MIXERCAPS2W capsW;
828 if (!caps)
829 return MMSYSERR_INVALPARAM;
831 if (!mmixer)
832 return MMSYSERR_BADDEVICEID;
834 memset(&capsW, 0, sizeof(MIXERCAPS2W));
836 capsW.wMid = WINE_MIXER_MANUF_ID;
837 capsW.wPid = WINE_MIXER_PRODUCT_ID;
838 capsW.vDriverVersion = WINE_MIXER_VERSION;
840 lstrcpynW(capsW.szPname, mmixer->mixername, sizeof(capsW.szPname)/sizeof(WCHAR));
841 capsW.cDestinations = mmixer->dests;
842 memcpy(caps, &capsW, min(parm2, sizeof(capsW)));
843 return MMSYSERR_NOERROR;
846 /* convert win32 volume to alsa volume, and vice versa */
847 static INT normalized(INT value, INT prevmax, INT nextmax)
849 int ret = MulDiv(value, nextmax, prevmax);
851 /* Have to stay in range */
852 TRACE("%d/%d -> %d/%d\n", value, prevmax, ret, nextmax);
853 if (ret > nextmax)
854 ret = nextmax;
855 else if (ret < 0)
856 ret = 0;
858 return ret;
861 /* get amount of sources for dest */
862 static int getsrccntfromchan(mixer *mmixer, int dad)
864 int i, j=0;
866 for (i=0; i<mmixer->chans; ++i)
867 if (i != dad && mmixer->lines[i].dst == dad)
869 ++j;
871 if (!j)
872 FIXME("No src found for %i (%s)?\n", dad, debugstr_w(mmixer->lines[dad].name));
873 return j;
876 /* find lineid for source 'num' with dest 'dad' */
877 static int getsrclinefromchan(mixer *mmixer, int dad, int num)
879 int i, j=0;
880 for (i=0; i<mmixer->chans; ++i)
881 if (i != dad && mmixer->lines[i].dst == dad)
883 if (num == j)
884 return i;
885 ++j;
887 WARN("No src found for src %i from dest %i\n", num, dad);
888 return 0;
891 /* get the source number belonging to line */
892 static int getsrcfromline(mixer *mmixer, int line)
894 int i, j=0, dad = mmixer->lines[line].dst;
896 for (i=0; i<mmixer->chans; ++i)
897 if (i != dad && mmixer->lines[i].dst == dad)
899 if (line == i)
900 return j;
901 ++j;
903 WARN("No src found for line %i with dad %i\n", line, dad);
904 return 0;
907 /* Get volume/muted/capture channel */
908 static DWORD MIX_GetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
910 mixer *mmixer = MIX_GetMix(wDevID);
911 DWORD ctrl;
912 DWORD line;
913 control *ct;
915 if (!mctrld)
916 return MMSYSERR_INVALPARAM;
918 ctrl = mctrld->dwControlID;
919 line = ctrl/CONTROLSPERLINE;
921 if (mctrld->cbStruct != sizeof(*mctrld))
922 return MMSYSERR_INVALPARAM;
924 if (!mmixer)
925 return MMSYSERR_BADDEVICEID;
927 if (line >= mmixer->chans || !mmixer->controls[ctrl].enabled)
928 return MIXERR_INVALCONTROL;
930 ct = &mmixer->controls[ctrl];
932 flags &= MIXER_GETCONTROLDETAILSF_QUERYMASK;
934 switch (flags) {
935 case MIXER_GETCONTROLDETAILSF_VALUE:
936 TRACE("MIXER_GETCONTROLDETAILSF_VALUE (%d/%d)\n", ctrl, line);
937 switch (ct->c.dwControlType)
939 case MIXERCONTROL_CONTROLTYPE_VOLUME:
941 long min = 0, max = 0, vol = 0;
942 int chn;
943 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
944 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
946 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
948 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
949 return MMSYSERR_INVALPARAM;
952 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
954 mcdu = mctrld->paDetails;
956 if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
958 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
959 return MMSYSERR_INVALPARAM;
962 if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem)) {
963 snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
964 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
965 if (snd_mixer_selem_has_capture_channel(elem, chn))
967 snd_mixer_selem_get_capture_volume(elem, chn, &vol);
968 mcdu->dwValue = normalized(vol - min, max, 65535);
969 if (mctrld->cChannels == 1)
970 break;
971 ++mcdu;
973 } else {
974 snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
976 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
977 if (snd_mixer_selem_has_playback_channel(elem, chn))
979 snd_mixer_selem_get_playback_volume(elem, chn, &vol);
980 mcdu->dwValue = normalized(vol - min, max, 65535);
981 if (mctrld->cChannels == 1)
982 break;
983 ++mcdu;
987 return MMSYSERR_NOERROR;
990 case MIXERCONTROL_CONTROLTYPE_ONOFF:
991 case MIXERCONTROL_CONTROLTYPE_MUTE:
993 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
994 int chn, ival;
995 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
997 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
999 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1000 return MMSYSERR_INVALPARAM;
1003 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1005 mcdb = mctrld->paDetails;
1007 if (line == 1)
1008 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1010 if (!snd_mixer_selem_has_capture_channel(elem, chn))
1011 continue;
1012 snd_mixer_selem_get_capture_switch(elem, chn, &ival);
1013 break;
1015 else
1016 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1018 if (!snd_mixer_selem_has_playback_channel(elem, chn))
1019 continue;
1020 snd_mixer_selem_get_playback_switch(elem, chn, &ival);
1021 break;
1024 if (chn > SND_MIXER_SCHN_LAST)
1026 TRACE("can't find active channel\n");
1027 return MMSYSERR_INVALPARAM; /* fixme: what's right error? */
1030 mcdb->fValue = !ival;
1031 TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
1032 return MMSYSERR_NOERROR;
1034 case MIXERCONTROL_CONTROLTYPE_MIXER:
1035 case MIXERCONTROL_CONTROLTYPE_MUX:
1037 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1038 int x, i=0, ival = 0, chn;
1040 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1042 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1043 return MMSYSERR_INVALPARAM;
1046 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1048 mcdb = mctrld->paDetails;
1050 for (x = 0; x<mmixer->chans; ++x)
1051 if (line != x && mmixer->lines[x].dst == line)
1053 ival = 0;
1054 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1056 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1057 continue;
1058 snd_mixer_selem_get_capture_switch(mmixer->lines[x].elem, chn, &ival);
1059 if (ival)
1060 break;
1062 if (i >= mctrld->u.cMultipleItems)
1064 TRACE("overflow\n");
1065 return MMSYSERR_INVALPARAM;
1067 TRACE("fVal[%i] = %sselected\n", i, (!ival ? "un" : ""));
1068 mcdb[i++].fValue = ival;
1070 break;
1072 default:
1074 FIXME("Unhandled controltype %s\n", getControlType(ct->c.dwControlType));
1075 return MMSYSERR_INVALPARAM;
1077 return MMSYSERR_NOERROR;
1079 case MIXER_GETCONTROLDETAILSF_LISTTEXT:
1080 TRACE("MIXER_GETCONTROLDETAILSF_LISTTEXT (%d)\n", ctrl);
1082 if (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX || ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER)
1084 LPMIXERCONTROLDETAILS_LISTTEXTW mcdlt = mctrld->paDetails;
1085 int i, j;
1087 for (i = j = 0; j < mmixer->chans; ++j)
1088 if (j != line && mmixer->lines[j].dst == line)
1090 if (i > mctrld->u.cMultipleItems)
1091 return MMSYSERR_INVALPARAM;
1092 mcdlt->dwParam1 = j;
1093 mcdlt->dwParam2 = mmixer->lines[j].component;
1094 lstrcpynW(mcdlt->szName, mmixer->lines[j].name, sizeof(mcdlt->szName) / sizeof(WCHAR));
1095 TRACE("Adding %i as %s\n", j, debugstr_w(mcdlt->szName));
1096 ++i; ++mcdlt;
1098 if (i < mctrld->u.cMultipleItems)
1099 return MMSYSERR_INVALPARAM;
1100 return MMSYSERR_NOERROR;
1102 FIXME ("Imagine this code being horribly broken and incomplete, introducing: reality\n");
1103 return MMSYSERR_INVALPARAM;
1105 default:
1106 WARN("Unknown flag (%08lx)\n", flags);
1107 return MMSYSERR_INVALPARAM;
1111 /* Set volume/capture channel/muted for control */
1112 static DWORD MIX_SetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
1114 mixer *mmixer = MIX_GetMix(wDevID);
1115 DWORD ctrl, line, i;
1116 control *ct;
1117 snd_mixer_elem_t * elem;
1119 if (!mctrld)
1120 return MMSYSERR_INVALPARAM;
1122 ctrl = mctrld->dwControlID;
1123 line = ctrl/CONTROLSPERLINE;
1125 if (mctrld->cbStruct != sizeof(*mctrld))
1127 WARN("Invalid size of mctrld %d\n", mctrld->cbStruct);
1128 return MMSYSERR_INVALPARAM;
1131 if (!mmixer)
1132 return MMSYSERR_BADDEVICEID;
1134 if (line >= mmixer->chans)
1136 WARN("Invalid line id: %d not in range of 0-%d\n", line, mmixer->chans-1);
1137 return MMSYSERR_INVALPARAM;
1140 if (!mmixer->controls[ctrl].enabled)
1142 WARN("Control %d not enabled\n", ctrl);
1143 return MIXERR_INVALCONTROL;
1146 ct = &mmixer->controls[ctrl];
1147 elem = mmixer->lines[line].elem;
1148 flags &= MIXER_SETCONTROLDETAILSF_QUERYMASK;
1150 switch (flags) {
1151 case MIXER_SETCONTROLDETAILSF_VALUE:
1152 TRACE("MIXER_SETCONTROLDETAILSF_VALUE (%d)\n", ctrl);
1153 break;
1155 default:
1156 WARN("Unknown flag (%08lx)\n", flags);
1157 return MMSYSERR_INVALPARAM;
1160 switch (ct->c.dwControlType)
1162 case MIXERCONTROL_CONTROLTYPE_VOLUME:
1164 long min = 0, max = 0;
1165 int chn;
1166 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
1167 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
1169 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
1171 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1172 return MMSYSERR_INVALPARAM;
1175 if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
1177 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
1178 return MMSYSERR_INVALPARAM;
1181 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1182 mcdu = mctrld->paDetails;
1184 for (chn=0; chn<mctrld->cChannels;++chn)
1186 TRACE("Chan %d value %d\n", chn, mcdu[chn].dwValue);
1189 /* There isn't always a capture volume, so in that case change playback volume */
1190 if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem))
1192 snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
1194 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1195 if (snd_mixer_selem_has_capture_channel(elem, chn))
1197 snd_mixer_selem_set_capture_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
1198 if (mctrld->cChannels != 1)
1199 mcdu++;
1202 else
1204 snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
1206 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1207 if (snd_mixer_selem_has_playback_channel(elem, chn))
1209 snd_mixer_selem_set_playback_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
1210 if (mctrld->cChannels != 1)
1211 mcdu++;
1215 break;
1217 case MIXERCONTROL_CONTROLTYPE_MUTE:
1218 case MIXERCONTROL_CONTROLTYPE_ONOFF:
1220 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1222 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1224 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1225 return MMSYSERR_INVALPARAM;
1228 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1230 mcdb = mctrld->paDetails;
1231 if (line == 1) /* Mute/unmute capturing */
1232 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
1234 if (snd_mixer_selem_has_capture_channel(elem, i))
1235 snd_mixer_selem_set_capture_switch(elem, i, !mcdb->fValue);
1237 else
1238 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
1239 if (snd_mixer_selem_has_playback_channel(elem, i))
1240 snd_mixer_selem_set_playback_switch(elem, i, !mcdb->fValue);
1241 break;
1244 case MIXERCONTROL_CONTROLTYPE_MIXER:
1245 case MIXERCONTROL_CONTROLTYPE_MUX:
1247 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1248 int x, i=0, chn;
1249 int didone = 0, canone = (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX);
1251 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1253 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1254 return MMSYSERR_INVALPARAM;
1257 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1258 mcdb = mctrld->paDetails;
1260 for (x=i=0; x < mmixer->chans; ++x)
1261 if (line != x && mmixer->lines[x].dst == line)
1263 TRACE("fVal[%i] (%s) = %i\n", i, debugstr_w(mmixer->lines[x].name), mcdb[i].fValue);
1264 if (i >= mctrld->u.cMultipleItems)
1266 TRACE("Too many items to fit, overflowing\n");
1267 return MIXERR_INVALVALUE;
1269 if (mcdb[i].fValue && canone && didone)
1271 TRACE("Nice try, but it's not going to work\n");
1272 elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
1273 return MIXERR_INVALVALUE;
1275 if (mcdb[i].fValue)
1276 didone = 1;
1277 ++i;
1280 if (canone && !didone)
1282 TRACE("Nice try, this is not going to work either\n");
1283 elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
1284 return MIXERR_INVALVALUE;
1287 for (x = i = 0; x<mmixer->chans; ++x)
1288 if (line != x && mmixer->lines[x].dst == line)
1290 if (mcdb[i].fValue)
1291 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1293 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1294 continue;
1295 snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
1297 ++i;
1300 /* If it's a MUX, it means that only 1 channel can be selected
1301 * and the other channels are unselected
1303 * For MIXER multiple sources are allowed, so unselect here
1305 if (canone)
1306 break;
1308 for (x = i = 0; x<mmixer->chans; ++x)
1309 if (line != x && mmixer->lines[x].dst == line)
1311 if (!mcdb[i].fValue)
1312 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1314 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1315 continue;
1316 snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
1318 ++i;
1320 break;
1322 default:
1323 FIXME("Unhandled type %s\n", getControlType(ct->c.dwControlType));
1324 return MMSYSERR_INVALPARAM;
1326 return MMSYSERR_NOERROR;
1329 /* Here we give info over the source/dest line given by dwSource+dwDest or dwDest, respectively
1330 * It is also possible that a line is found by componenttype or target type, latter is not implemented yet
1331 * Most important values returned in struct:
1332 * dwLineID
1333 * sz(Short)Name
1334 * line control count
1335 * amount of channels
1337 static DWORD MIX_GetLineInfo(UINT wDevID, LPMIXERLINEW Ml, DWORD_PTR flags)
1339 DWORD_PTR qf = flags & MIXER_GETLINEINFOF_QUERYMASK;
1340 mixer *mmixer = MIX_GetMix(wDevID);
1341 line *mline;
1342 int idx, i;
1344 if (!Ml)
1346 WARN("No Ml\n");
1347 return MMSYSERR_INVALPARAM;
1350 if (!mmixer)
1352 WARN("Device %u not found\n", wDevID);
1353 return MMSYSERR_BADDEVICEID;
1356 if (Ml->cbStruct != sizeof(*Ml))
1358 WARN("invalid parameter: Ml->cbStruct = %d\n", Ml->cbStruct);
1359 return MMSYSERR_INVALPARAM;
1362 Ml->dwUser = 0;
1363 Ml->fdwLine = MIXERLINE_LINEF_DISCONNECTED;
1364 switch (qf)
1366 case MIXER_GETLINEINFOF_COMPONENTTYPE:
1368 Ml->dwLineID = 0xFFFF;
1369 TRACE("Looking for componenttype %d/%x\n", Ml->dwComponentType, Ml->dwComponentType);
1370 for (idx = 0; idx < mmixer->chans; ++idx)
1371 if (mmixer->lines[idx].component == Ml->dwComponentType)
1373 Ml->dwLineID = idx;
1374 break;
1376 if (Ml->dwLineID == 0xFFFF)
1377 return MMSYSERR_KEYNOTFOUND;
1378 /* Now that we have lineid, fallback to lineid*/
1381 case MIXER_GETLINEINFOF_LINEID:
1382 if (Ml->dwLineID >= mmixer->chans)
1383 return MIXERR_INVALLINE;
1385 TRACE("MIXER_GETLINEINFOF_LINEID %d\n", Ml->dwLineID);
1386 Ml->dwDestination = mmixer->lines[Ml->dwLineID].dst;
1388 if (Ml->dwDestination != Ml->dwLineID)
1390 Ml->dwSource = getsrcfromline(mmixer, Ml->dwLineID);
1391 Ml->cConnections = 1;
1393 else
1395 Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
1396 Ml->dwSource = 0xFFFFFFFF;
1398 TRACE("Connections %d, source %d\n", Ml->cConnections, Ml->dwSource);
1399 break;
1401 case MIXER_GETLINEINFOF_DESTINATION:
1402 if (Ml->dwDestination >= mmixer->dests)
1404 WARN("dest %d out of bounds\n", Ml->dwDestination);
1405 return MIXERR_INVALLINE;
1408 Ml->dwLineID = Ml->dwDestination;
1409 Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
1410 Ml->dwSource = 0xFFFFFFFF;
1411 break;
1413 case MIXER_GETLINEINFOF_SOURCE:
1414 if (Ml->dwDestination >= mmixer->dests)
1416 WARN("dest %d for source out of bounds\n", Ml->dwDestination);
1417 return MIXERR_INVALLINE;
1420 if (Ml->dwSource >= getsrccntfromchan(mmixer, Ml->dwDestination))
1422 WARN("src %d out of bounds\n", Ml->dwSource);
1423 return MIXERR_INVALLINE;
1426 Ml->dwLineID = getsrclinefromchan(mmixer, Ml->dwDestination, Ml->dwSource);
1427 Ml->cConnections = 1;
1428 break;
1430 case MIXER_GETLINEINFOF_TARGETTYPE:
1431 FIXME("TODO: TARGETTYPE, stub\n");
1432 return MMSYSERR_INVALPARAM;
1434 default:
1435 FIXME("Unknown query flag: %08lx\n", qf);
1436 return MMSYSERR_INVALPARAM;
1439 Ml->fdwLine &= ~MIXERLINE_LINEF_DISCONNECTED;
1440 Ml->fdwLine |= MIXERLINE_LINEF_ACTIVE;
1441 if (Ml->dwLineID >= mmixer->dests)
1442 Ml->fdwLine |= MIXERLINE_LINEF_SOURCE;
1444 mline = &mmixer->lines[Ml->dwLineID];
1445 Ml->dwComponentType = mline->component;
1446 Ml->cChannels = mmixer->lines[Ml->dwLineID].chans;
1447 Ml->cControls = 0;
1449 for (i=CONTROLSPERLINE*Ml->dwLineID;i<CONTROLSPERLINE*(Ml->dwLineID+1); ++i)
1450 if (mmixer->controls[i].enabled)
1451 ++(Ml->cControls);
1453 lstrcpynW(Ml->szShortName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szShortName)/sizeof(WCHAR));
1454 lstrcpynW(Ml->szName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szName)/sizeof(WCHAR));
1455 if (mline->capt)
1456 Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
1457 else
1458 Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
1459 Ml->Target.dwDeviceID = 0xFFFFFFFF;
1460 Ml->Target.wMid = WINE_MIXER_MANUF_ID;
1461 Ml->Target.wPid = WINE_MIXER_PRODUCT_ID;
1462 Ml->Target.vDriverVersion = WINE_MIXER_VERSION;
1463 lstrcpynW(Ml->Target.szPname, mmixer->mixername, sizeof(Ml->Target.szPname)/sizeof(WCHAR));
1464 return MMSYSERR_NOERROR;
1467 /* Get the controls that belong to a certain line, either all or 1 */
1468 static DWORD MIX_GetLineControls(UINT wDevID, LPMIXERLINECONTROLSW mlc, DWORD_PTR flags)
1470 mixer *mmixer = MIX_GetMix(wDevID);
1471 int i,j = 0;
1472 DWORD ct;
1474 if (!mlc || mlc->cbStruct != sizeof(*mlc))
1476 WARN("Invalid mlc %p, cbStruct: %d\n", mlc, (!mlc ? -1 : mlc->cbStruct));
1477 return MMSYSERR_INVALPARAM;
1480 if (mlc->cbmxctrl != sizeof(MIXERCONTROLW))
1482 WARN("cbmxctrl %d\n", mlc->cbmxctrl);
1483 return MMSYSERR_INVALPARAM;
1486 if (!mmixer)
1487 return MMSYSERR_BADDEVICEID;
1489 flags &= MIXER_GETLINECONTROLSF_QUERYMASK;
1491 if (flags == MIXER_GETLINECONTROLSF_ONEBYID)
1492 mlc->dwLineID = mlc->u.dwControlID / CONTROLSPERLINE;
1494 if (mlc->dwLineID >= mmixer->chans)
1496 TRACE("Invalid dwLineID %d\n", mlc->dwLineID);
1497 return MIXERR_INVALLINE;
1500 switch (flags)
1502 case MIXER_GETLINECONTROLSF_ALL:
1503 TRACE("line=%08x MIXER_GETLINECONTROLSF_ALL (%d)\n", mlc->dwLineID, mlc->cControls);
1504 for (i = 0; i < CONTROLSPERLINE; ++i)
1505 if (mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].enabled)
1507 memcpy(&mlc->pamxctrl[j], &mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].c, sizeof(MIXERCONTROLW));
1508 TRACE("Added %s (%s)\n", debugstr_w(mlc->pamxctrl[j].szShortName), debugstr_w(mlc->pamxctrl[j].szName));
1509 ++j;
1510 if (j > mlc->cControls)
1512 WARN("invalid parameter\n");
1513 return MMSYSERR_INVALPARAM;
1517 if (!j || mlc->cControls > j)
1519 WARN("invalid parameter\n");
1520 return MMSYSERR_INVALPARAM;
1522 break;
1523 case MIXER_GETLINECONTROLSF_ONEBYID:
1524 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYID (%x)\n", mlc->dwLineID, mlc->u.dwControlID);
1526 if (!mmixer->controls[mlc->u.dwControlID].enabled)
1527 return MIXERR_INVALCONTROL;
1529 mlc->pamxctrl[0] = mmixer->controls[mlc->u.dwControlID].c;
1530 break;
1531 case MIXER_GETLINECONTROLSF_ONEBYTYPE:
1532 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYTYPE (%s)\n", mlc->dwLineID, getControlType(mlc->u.dwControlType));
1534 ct = mlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK;
1535 for (i = 0; i <= CONTROLSPERLINE; ++i)
1537 const int ofs = i+mlc->dwLineID*CONTROLSPERLINE;
1538 if (i == CONTROLSPERLINE)
1540 WARN("invalid parameter: control %s not found\n", getControlType(mlc->u.dwControlType));
1541 return MIXERR_INVALCONTROL;
1543 if (mmixer->controls[ofs].enabled && (mmixer->controls[ofs].c.dwControlType & MIXERCONTROL_CT_CLASS_MASK) == ct)
1545 mlc->pamxctrl[0] = mmixer->controls[ofs].c;
1546 break;
1549 break;
1550 default:
1551 FIXME("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
1552 return MMSYSERR_INVALPARAM;
1555 return MMSYSERR_NOERROR;
1558 /**************************************************************************
1559 * mxdMessage (WINEALSA.3)
1561 DWORD WINAPI ALSA_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
1562 DWORD_PTR dwParam1, DWORD_PTR dwParam2)
1564 DWORD ret;
1565 TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID, getMessage(wMsg),
1566 dwUser, dwParam1, dwParam2);
1568 switch (wMsg)
1570 case DRVM_INIT: ALSA_MixerInit(); ret = MMSYSERR_NOERROR; break;
1571 case DRVM_EXIT: ALSA_MixerExit(); ret = MMSYSERR_NOERROR; break;
1572 /* All taken care of by driver initialisation */
1573 /* Unimplemented, and not needed */
1574 case DRVM_ENABLE:
1575 case DRVM_DISABLE:
1576 ret = MMSYSERR_NOERROR; break;
1578 case MXDM_OPEN:
1579 ret = MIX_Open(wDevID, (LPMIXEROPENDESC) dwParam1, dwParam2); break;
1581 case MXDM_CLOSE:
1582 ret = MIX_Close(wDevID); break;
1584 case MXDM_GETDEVCAPS:
1585 ret = MIX_GetDevCaps(wDevID, (LPMIXERCAPS2W)dwParam1, dwParam2); break;
1587 case MXDM_GETLINEINFO:
1588 ret = MIX_GetLineInfo(wDevID, (LPMIXERLINEW)dwParam1, dwParam2); break;
1590 case MXDM_GETLINECONTROLS:
1591 ret = MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSW)dwParam1, dwParam2); break;
1593 case MXDM_GETCONTROLDETAILS:
1594 ret = MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
1596 case MXDM_SETCONTROLDETAILS:
1597 ret = MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
1599 case MXDM_GETNUMDEVS:
1600 ret = cards; break;
1602 default:
1603 WARN("unknown message %s!\n", getMessage(wMsg));
1604 return MMSYSERR_NOTSUPPORTED;
1607 TRACE("Returning %08X\n", ret);
1608 return ret;