wer: Add new stubbed wer.dll.
[wine/hramrach.git] / dlls / winealsa.drv / mixer.c
blobffa02929049e364cb1e2838551a6308b7314a3a6
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 #ifdef HAVE_ALSA
57 #define WINE_MIXER_MANUF_ID 0xAA
58 #define WINE_MIXER_PRODUCT_ID 0x55
59 #define WINE_MIXER_VERSION 0x0100
61 /* Generic notes:
62 * In windows it seems to be required for all controls to have a volume switch
63 * In alsa that's optional
65 * I assume for playback controls, that there is always a playback volume switch available
66 * Mute is optional
68 * For capture controls, it is needed that there is a capture switch and a volume switch,
69 * It doesn't matter whether it is a playback volume switch or a capture volume switch.
70 * The code will first try to get/adjust capture volume, if that fails it tries playback volume
71 * It is not pretty, but under my 3 test cards it seems that there is no other choice:
72 * Most capture controls don't have a capture volume setting
74 * MUX means that only capture source can be exclusively selected,
75 * MIXER means that multiple sources can be selected simultaneously.
78 static const char * getMessage(UINT uMsg)
80 #define MSG_TO_STR(x) case x: return #x;
81 switch (uMsg){
82 MSG_TO_STR(DRVM_INIT);
83 MSG_TO_STR(DRVM_EXIT);
84 MSG_TO_STR(DRVM_ENABLE);
85 MSG_TO_STR(DRVM_DISABLE);
86 MSG_TO_STR(MXDM_GETDEVCAPS);
87 MSG_TO_STR(MXDM_GETLINEINFO);
88 MSG_TO_STR(MXDM_GETNUMDEVS);
89 MSG_TO_STR(MXDM_OPEN);
90 MSG_TO_STR(MXDM_CLOSE);
91 MSG_TO_STR(MXDM_GETLINECONTROLS);
92 MSG_TO_STR(MXDM_GETCONTROLDETAILS);
93 MSG_TO_STR(MXDM_SETCONTROLDETAILS);
94 default: break;
96 #undef MSG_TO_STR
97 return wine_dbg_sprintf("UNKNOWN(%08x)", uMsg);
100 static const char * getControlType(DWORD dwControlType)
102 #define TYPE_TO_STR(x) case x: return #x;
103 switch (dwControlType) {
104 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM);
105 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER);
106 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER);
107 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER);
108 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER);
109 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN);
110 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF);
111 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE);
112 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO);
113 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS);
114 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH);
115 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST);
116 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON);
117 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS);
118 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED);
119 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED);
120 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT);
121 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER);
122 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN);
123 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN);
124 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER);
125 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME);
126 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS);
127 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE);
128 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER);
129 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT);
130 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX);
131 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT);
132 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER);
133 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME);
134 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME);
136 #undef TYPE_TO_STR
137 return wine_dbg_sprintf("UNKNOWN(%08x)", dwControlType);
140 /* A simple declaration of a line control
141 * These are each of the channels that show up
143 typedef struct line {
144 /* Name we present to outside world */
145 WCHAR name[MAXPNAMELEN];
147 DWORD component;
148 DWORD dst;
149 DWORD capt;
150 DWORD chans;
151 snd_mixer_elem_t *elem;
152 } line;
154 /* A control structure, with toggle enabled switch
155 * Control structures control volume, muted, which capture source
157 typedef struct control {
158 BOOL enabled;
159 MIXERCONTROLW c;
160 } control;
162 /* Mixer device */
163 typedef struct mixer
165 snd_mixer_t *mix;
166 WCHAR mixername[MAXPNAMELEN];
168 int chans, dests;
169 LPDRVCALLBACK callback;
170 DWORD_PTR callbackpriv;
171 HDRVR hmx;
173 line *lines;
174 control *controls;
175 } mixer;
177 #define MAX_MIXERS 32
178 #define CONTROLSPERLINE 3
179 #define OFS_MUTE 2
180 #define OFS_MUX 1
182 static int cards = 0;
183 static mixer mixdev[MAX_MIXERS];
184 static HANDLE thread;
185 static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask);
186 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam);
187 static CRITICAL_SECTION elem_crst;
188 static int msg_pipe[2];
189 static LONG refcnt;
191 /* found channel names in alsa lib, alsa api doesn't have another way for this
192 * map name -> componenttype, worst case we get a wrong componenttype which is
193 * mostly harmless
196 static const struct mixerlinetype {
197 const char *name; DWORD cmpt;
198 } converttable[] = {
199 { "Master", MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, },
200 { "Capture", MIXERLINE_COMPONENTTYPE_DST_WAVEIN, },
201 { "PCM", MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT, },
202 { "PC Speaker", MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER, },
203 { "Synth", MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER, },
204 { "Headphone", MIXERLINE_COMPONENTTYPE_DST_HEADPHONES, },
205 { "Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
206 { "Aux", MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED, },
207 { "CD", MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC, },
208 { "Line", MIXERLINE_COMPONENTTYPE_SRC_LINE, },
209 { "Phone", MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE, },
210 { "Digital", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
211 { "Front Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
214 /* Map name to MIXERLINE_COMPONENTTYPE_XXX */
215 static int getcomponenttype(const char *name)
217 int x;
218 for (x=0; x< sizeof(converttable)/sizeof(converttable[0]); ++x)
219 if (!strcasecmp(name, converttable[x].name))
221 TRACE("%d -> %s\n", x, name);
222 return converttable[x].cmpt;
224 WARN("Unknown mixer name %s, probably harmless\n", name);
225 return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
228 /* Is this control suited for showing up? */
229 static int blacklisted(snd_mixer_elem_t *elem)
231 const char *name = snd_mixer_selem_get_name(elem);
232 BOOL blisted = 0;
234 if (!snd_mixer_selem_has_playback_volume(elem) &&
235 !snd_mixer_selem_has_capture_volume(elem))
236 blisted = 1;
238 TRACE("%s: %x\n", name, blisted);
239 return blisted;
242 static void fillcontrols(mixer *mmixer)
244 int id;
245 for (id = 0; id < mmixer->chans; ++id)
247 line *mline = &mmixer->lines[id];
248 int ofs = CONTROLSPERLINE * id;
249 int x;
250 long min, max;
252 TRACE("Filling control %d\n", id);
253 if (!mline->elem)
254 break;
255 if (id == 1 && !mline->elem)
256 continue;
258 if (mline->capt && snd_mixer_selem_has_capture_volume(mline->elem))
259 snd_mixer_selem_get_capture_volume_range(mline->elem, &min, &max);
260 else
261 snd_mixer_selem_get_playback_volume_range(mline->elem, &min, &max);
263 /* (!snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) */
264 /* Volume, always enabled by definition of blacklisted channels */
265 mmixer->controls[ofs].enabled = 1;
266 mmixer->controls[ofs].c.cbStruct = sizeof(mmixer->controls[ofs].c);
267 mmixer->controls[ofs].c.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
268 mmixer->controls[ofs].c.dwControlID = ofs;
269 mmixer->controls[ofs].c.Bounds.s1.dwMinimum = 0;
270 mmixer->controls[ofs].c.Bounds.s1.dwMaximum = 65535;
271 mmixer->controls[ofs].c.Metrics.cSteps = 65536/(max-min);
273 if ((id == 1 && snd_mixer_selem_has_capture_switch(mline->elem)) ||
274 (!mline->capt && snd_mixer_selem_has_playback_switch(mline->elem)))
275 { /* MUTE button optional, main capture channel should have one too */
276 mmixer->controls[ofs+OFS_MUTE].enabled = 1;
277 mmixer->controls[ofs+OFS_MUTE].c.cbStruct = sizeof(mmixer->controls[ofs].c);
278 mmixer->controls[ofs+OFS_MUTE].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
279 mmixer->controls[ofs+OFS_MUTE].c.dwControlID = ofs+OFS_MUTE;
280 mmixer->controls[ofs+OFS_MUTE].c.Bounds.s1.dwMaximum = 1;
283 if (mline->capt && snd_mixer_selem_has_capture_switch_exclusive(mline->elem))
284 mmixer->controls[CONTROLSPERLINE+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUX;
286 if (id == 1)
287 { /* Capture select, in case cMultipleItems is 0, it means capture is disabled anyway */
288 mmixer->controls[ofs+OFS_MUX].enabled = 1;
289 mmixer->controls[ofs+OFS_MUX].c.cbStruct = sizeof(mmixer->controls[ofs].c);
290 mmixer->controls[ofs+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MIXER;
291 mmixer->controls[ofs+OFS_MUX].c.dwControlID = ofs+OFS_MUX;
292 mmixer->controls[ofs+OFS_MUX].c.fdwControl = MIXERCONTROL_CONTROLF_MULTIPLE;
294 for (x = 0; x<mmixer->chans; ++x)
295 if (x != id && mmixer->lines[x].dst == id)
296 ++(mmixer->controls[ofs+OFS_MUX].c.cMultipleItems);
297 if (!mmixer->controls[ofs+OFS_MUX].c.cMultipleItems)
298 mmixer->controls[ofs+OFS_MUX].enabled = 0;
300 mmixer->controls[ofs+OFS_MUX].c.Bounds.s1.dwMaximum = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems - 1;
301 mmixer->controls[ofs+OFS_MUX].c.Metrics.cSteps = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems;
303 for (x=0; x<CONTROLSPERLINE; ++x)
305 lstrcpynW(mmixer->controls[ofs+x].c.szShortName, mline->name, sizeof(mmixer->controls[ofs+x].c.szShortName)/sizeof(WCHAR));
306 lstrcpynW(mmixer->controls[ofs+x].c.szName, mline->name, sizeof(mmixer->controls[ofs+x].c.szName)/sizeof(WCHAR));
311 /* get amount of channels for elem */
312 /* Officially we should keep capture/playback separated,
313 * but that's not going to work in the alsa api */
314 static int chans(mixer *mmixer, snd_mixer_elem_t * elem, DWORD capt)
316 int ret=0, chn;
318 if (capt && snd_mixer_selem_has_capture_volume(elem)) {
319 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
320 if (snd_mixer_selem_has_capture_channel(elem, chn))
321 ++ret;
322 } else {
323 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
324 if (snd_mixer_selem_has_playback_channel(elem, chn))
325 ++ret;
327 if (!ret)
328 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"));
329 return ret;
332 static void filllines(mixer *mmixer, snd_mixer_elem_t *mastelem, snd_mixer_elem_t *captelem, int capt)
334 snd_mixer_elem_t *elem;
335 line *mline = mmixer->lines;
337 if (mastelem) {
338 /* Master control */
339 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(mastelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
340 mline->component = getcomponenttype(snd_mixer_selem_get_name(mastelem));
341 mline->dst = 0;
342 mline->capt = 0;
343 mline->elem = mastelem;
344 mline->chans = chans(mmixer, mastelem, 0);
346 snd_mixer_elem_set_callback(mastelem, &elem_callback);
347 snd_mixer_elem_set_callback_private(mastelem, mmixer);
348 } else {
349 MultiByteToWideChar(CP_UNIXCP, 0, "Empty Master Element", -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
352 /* Capture control
353 * Note: since mmixer->dests = 1, it means only playback control is visible
354 * This makes sense, because if there are no capture sources capture control
355 * can't do anything and should be invisible */
357 /* Control 1 is reserved for capture even when not enabled */
358 ++mline;
359 if (capt)
361 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
362 mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem));
363 mline->dst = 1;
364 mline->capt = 1;
365 mline->elem = captelem;
366 mline->chans = chans(mmixer, captelem, 1);
368 snd_mixer_elem_set_callback(captelem, &elem_callback);
369 snd_mixer_elem_set_callback_private(captelem, mmixer);
372 for (elem = snd_mixer_first_elem(mmixer->mix); elem; elem = snd_mixer_elem_next(elem))
373 if (elem != mastelem && elem != captelem && !blacklisted(elem))
375 const char * name = snd_mixer_selem_get_name(elem);
376 DWORD comp = getcomponenttype(name);
378 if (snd_mixer_selem_has_playback_volume(elem) &&
379 (snd_mixer_selem_has_capture_volume(elem) || comp != MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE))
381 (++mline)->component = comp;
382 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
383 mline->capt = mline->dst = 0;
384 mline->elem = elem;
385 mline->chans = chans(mmixer, elem, 0);
387 else if (!capt)
388 continue;
390 if (capt && (snd_mixer_selem_has_capture_volume(elem) || comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE))
392 (++mline)->component = comp;
393 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
394 mline->capt = mline->dst = 1;
395 mline->elem = elem;
396 mline->chans = chans(mmixer, elem, 1);
399 snd_mixer_elem_set_callback(elem, &elem_callback);
400 snd_mixer_elem_set_callback_private(elem, mmixer);
404 static void filllines_no_master(mixer *mmixer, snd_mixer_elem_t *captelem, int capt)
406 line *mline = mmixer->lines;
408 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
409 mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem));
410 mline->dst = 0;
411 mline->capt = 1;
412 mline->elem = captelem;
413 mline->chans = chans(mmixer, captelem, 1);
415 snd_mixer_elem_set_callback(captelem, &elem_callback);
416 snd_mixer_elem_set_callback_private(captelem, mmixer);
419 /* Windows api wants to have a 'master' device to which all slaves are attached
420 * There are 2 ones in this code:
421 * - 'Master', fall back to 'Headphone' if unavailable, and if that's not available 'PCM'
422 * - 'Capture'
423 * Capture might not always be available, so should be prepared to be without if needed
426 static void ALSA_MixerInit(void)
428 int x, mixnum = 0;
429 snd_ctl_card_info_t *info;
431 info = HeapAlloc( GetProcessHeap(), 0, snd_ctl_card_info_sizeof());
432 for (x = 0; x < MAX_MIXERS; ++x)
434 int card, err, capcontrols = 0;
435 char cardind[6], cardname[10];
437 snd_ctl_t *ctl;
438 snd_mixer_elem_t *elem, *mastelem = NULL, *headelem = NULL, *captelem = NULL, *pcmelem = NULL, *micelem = NULL;
440 memset(info, 0, snd_ctl_card_info_sizeof());
441 memset(&mixdev[mixnum], 0, sizeof(*mixdev));
442 snprintf(cardind, sizeof(cardind), "%d", x);
443 card = snd_card_get_index(cardind);
444 if (card < 0)
445 continue;
447 snprintf(cardname, sizeof(cardname), "hw:%d", card);
449 err = snd_ctl_open(&ctl, cardname, 0);
450 if (err < 0)
452 WARN("Cannot open card: %s\n", snd_strerror(err));
453 continue;
456 err = snd_ctl_card_info(ctl, info);
457 if (err < 0)
459 WARN("Cannot get card info: %s\n", snd_strerror(err));
460 snd_ctl_close(ctl);
461 continue;
464 MultiByteToWideChar(CP_UNIXCP, 0, snd_ctl_card_info_get_name(info), -1, mixdev[mixnum].mixername, sizeof(mixdev[mixnum].mixername)/sizeof(WCHAR));
465 snd_ctl_close(ctl);
467 err = snd_mixer_open(&mixdev[mixnum].mix, 0);
468 if (err < 0)
470 WARN("Error occurred opening mixer: %s\n", snd_strerror(err));
471 continue;
474 err = snd_mixer_attach(mixdev[mixnum].mix, cardname);
475 if (err < 0)
476 goto eclose;
478 err = snd_mixer_selem_register(mixdev[mixnum].mix, NULL, NULL);
479 if (err < 0)
480 goto eclose;
482 err = snd_mixer_load(mixdev[mixnum].mix);
483 if (err < 0)
484 goto eclose;
486 /* First, lets see what's available..
487 * If there are multiple Master or Captures, all except 1 will be added as slaves
489 for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
490 if (!strcasecmp(snd_mixer_selem_get_name(elem), "Master") && !mastelem)
491 mastelem = elem;
492 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Capture") && !captelem)
493 captelem = elem;
494 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Mic") && !micelem && !mastelem)
495 /* this is what snd-usb-audio mics look like; just a Mic control and that's it.*/
496 micelem = elem;
497 else if (!blacklisted(elem))
499 DWORD comp = getcomponenttype(snd_mixer_selem_get_name(elem));
500 DWORD skip = 0;
502 /* Work around buggy drivers: Make this a capture control if the name is recognised as a microphone */
503 if (snd_mixer_selem_has_capture_volume(elem))
504 ++capcontrols;
505 else if (comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)
507 ++capcontrols;
508 skip = 1;
511 if (!skip && snd_mixer_selem_has_playback_volume(elem))
513 if (!strcasecmp(snd_mixer_selem_get_name(elem), "Headphone") && !headelem)
514 headelem = elem;
515 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "PCM") && !pcmelem)
516 pcmelem = elem;
517 else
518 ++(mixdev[mixnum].chans);
522 /* Add master channel, uncounted channels and an extra for capture */
523 mixdev[mixnum].chans += !!mastelem + !!headelem + !!pcmelem + 1;
525 /* If there is only 'Capture' and 'Master', this device is not worth it */
526 if (mixdev[mixnum].chans == 2)
528 WARN("No channels found, skipping device!\n");
529 goto close;
532 /* Master element can't have a capture control in this code, so
533 * if Headphone or PCM is promoted to master, unset its capture control */
534 if (headelem && !mastelem)
536 /* Using 'Headphone' as master device */
537 mastelem = headelem;
538 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
540 else if (pcmelem && !mastelem)
542 /* Use 'PCM' as master device */
543 mastelem = pcmelem;
544 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
546 else if (!mastelem && !captelem && !micelem)
548 /* If there is nothing sensible that can act as 'Master' control, something is wrong */
549 FIXME("No master control found on %s, disabling mixer\n", snd_ctl_card_info_get_name(info));
550 goto close;
553 if (!captelem || !capcontrols)
555 /* Can't enable capture, so disabling it
556 * Note: capture control will still exist because
557 * dwLineID 0 and 1 are reserved for Master and Capture
559 WARN("No use enabling capture part of mixer, capture control found: %s, amount of capture controls: %d\n",
560 (!captelem ? "no" : "yes"), capcontrols);
561 capcontrols = 0;
562 mixdev[mixnum].dests = 1;
564 else
566 mixdev[mixnum].chans += capcontrols;
567 mixdev[mixnum].dests = 2;
570 mixdev[mixnum].lines = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(line) * mixdev[mixnum].chans);
571 mixdev[mixnum].controls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(control) * CONTROLSPERLINE*mixdev[mixnum].chans);
572 err = -ENOMEM;
573 if (!mixdev[mixnum].lines || !mixdev[mixnum].controls)
574 goto close;
576 if (mastelem)
577 filllines(&mixdev[mixnum], mastelem, captelem, capcontrols);
578 else if (micelem)
579 filllines_no_master(&mixdev[mixnum], micelem, 1);
580 fillcontrols(&mixdev[mixnum]);
582 TRACE("%s: Amount of controls: %i/%i, name: %s\n", cardname, mixdev[mixnum].dests, mixdev[mixnum].chans, debugstr_w(mixdev[mixnum].mixername));
583 mixnum++;
584 continue;
586 eclose:
587 WARN("Error occurred initialising mixer: %s\n", snd_strerror(err));
588 close:
589 HeapFree(GetProcessHeap(), 0, mixdev[mixnum].lines);
590 HeapFree(GetProcessHeap(), 0, mixdev[mixnum].controls);
591 snd_mixer_close(mixdev[mixnum].mix);
593 cards = mixnum;
594 HeapFree( GetProcessHeap(), 0, info );
596 /* There is no trouble with already assigning callbacks without initialising critsect:
597 * Callbacks only occur when snd_mixer_handle_events is called (only happens in thread)
599 InitializeCriticalSection(&elem_crst);
600 elem_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_MIXER.elem_crst");
601 TRACE("\n");
604 static void ALSA_MixerExit(void)
606 int x;
608 if (refcnt)
610 WARN("Callback thread still alive, terminating uncleanly, refcnt: %d\n", refcnt);
611 /* Least we can do is making sure we're not in 'foreign' code */
612 EnterCriticalSection(&elem_crst);
613 TerminateThread(thread, 1);
614 refcnt = 0;
615 LeaveCriticalSection(&elem_crst);
618 TRACE("Cleaning up\n");
620 elem_crst.DebugInfo->Spare[0] = 0;
621 DeleteCriticalSection(&elem_crst);
622 for (x = 0; x < cards; ++x)
624 snd_mixer_close(mixdev[x].mix);
625 HeapFree(GetProcessHeap(), 0, mixdev[x].lines);
626 HeapFree(GetProcessHeap(), 0, mixdev[x].controls);
628 cards = 0;
631 static mixer* MIX_GetMix(UINT wDevID)
633 mixer *mmixer;
635 if (wDevID >= cards)
637 WARN("Invalid mixer id: %d\n", wDevID);
638 return NULL;
641 mmixer = &mixdev[wDevID];
642 return mmixer;
645 /* Since alsa doesn't tell what exactly changed, just assume all affected controls changed */
646 static int elem_callback(snd_mixer_elem_t *elem, unsigned int type)
648 mixer *mmixer = snd_mixer_elem_get_callback_private(elem);
649 int x;
650 BOOL captchanged = 0;
652 if (type != SND_CTL_EVENT_MASK_VALUE)
653 return 0;
655 assert(mmixer);
657 EnterCriticalSection(&elem_crst);
659 if (!mmixer->callback)
660 goto out;
662 for (x=0; x<mmixer->chans; ++x)
664 const int ofs = CONTROLSPERLINE*x;
665 if (elem != mmixer->lines[x].elem)
666 continue;
668 if (mmixer->lines[x].capt)
669 ++captchanged;
671 TRACE("Found changed control %s\n", debugstr_w(mmixer->lines[x].name));
672 mmixer->callback(mmixer->hmx, MM_MIXM_LINE_CHANGE, mmixer->callbackpriv, x, 0);
673 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs, 0);
675 if (mmixer->controls[ofs+OFS_MUTE].enabled)
676 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs+OFS_MUTE, 0);
678 if (captchanged)
679 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, CONTROLSPERLINE+OFS_MUX, 0);
681 out:
682 LeaveCriticalSection(&elem_crst);
684 return 0;
687 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam)
689 struct pollfd *pfds = NULL;
690 int x, y, err, mcnt, count = 1;
692 TRACE("%p\n", lParam);
694 for (x = 0; x < cards; ++x)
695 count += snd_mixer_poll_descriptors_count(mixdev[x].mix);
697 TRACE("Counted %d descriptors\n", count);
698 pfds = HeapAlloc(GetProcessHeap(), 0, count * sizeof(struct pollfd));
700 if (!pfds)
702 WARN("Out of memory\n");
703 goto die;
706 pfds[0].fd = msg_pipe[0];
707 pfds[0].events = POLLIN;
709 y = 1;
710 for (x = 0; x < cards; ++x)
711 y += snd_mixer_poll_descriptors(mixdev[x].mix, &pfds[y], count - y);
713 while ((err = poll(pfds, (unsigned int) count, -1)) >= 0 || errno == EINTR || errno == EAGAIN)
715 if (pfds[0].revents & POLLIN)
716 break;
718 mcnt = 1;
719 for (x = y = 0; x < cards; ++x)
721 int j, max = snd_mixer_poll_descriptors_count(mixdev[x].mix);
722 for (j = 0; j < max; ++j)
723 if (pfds[mcnt+j].revents)
725 y += snd_mixer_handle_events(mixdev[x].mix);
726 break;
728 mcnt += max;
730 if (y)
731 TRACE("Handled %d events\n", y);
734 die:
735 TRACE("Shutting down\n");
736 HeapFree(GetProcessHeap(), 0, pfds);
738 y = read(msg_pipe[0], &x, sizeof(x));
739 close(msg_pipe[1]);
740 close(msg_pipe[0]);
741 return 0;
744 static DWORD MIX_Open(UINT wDevID, LPMIXEROPENDESC desc, DWORD_PTR flags)
746 mixer *mmixer = MIX_GetMix(wDevID);
747 if (!mmixer)
748 return MMSYSERR_BADDEVICEID;
750 flags &= CALLBACK_TYPEMASK;
751 switch (flags)
753 case CALLBACK_NULL:
754 goto done;
756 case CALLBACK_FUNCTION:
757 break;
759 default:
760 FIXME("Unhandled callback type: %08lx\n", flags & CALLBACK_TYPEMASK);
761 return MIXERR_INVALVALUE;
764 mmixer->callback = (LPDRVCALLBACK)desc->dwCallback;
765 mmixer->callbackpriv = desc->dwInstance;
766 mmixer->hmx = (HDRVR)desc->hmx;
768 done:
769 if (InterlockedIncrement(&refcnt) == 1)
771 if (pipe(msg_pipe) >= 0)
773 thread = CreateThread(NULL, 0, ALSA_MixerPollThread, NULL, 0, NULL);
774 if (!thread)
776 close(msg_pipe[0]);
777 close(msg_pipe[1]);
778 msg_pipe[0] = msg_pipe[1] = -1;
781 else
782 msg_pipe[0] = msg_pipe[1] = -1;
785 return MMSYSERR_NOERROR;
788 static DWORD MIX_Close(UINT wDevID)
790 int x = 0;
791 mixer *mmixer = MIX_GetMix(wDevID);
792 if (!mmixer)
793 return MMSYSERR_BADDEVICEID;
795 EnterCriticalSection(&elem_crst);
796 mmixer->callback = 0;
797 LeaveCriticalSection(&elem_crst);
799 if (!InterlockedDecrement(&refcnt))
801 if (write(msg_pipe[1], &x, sizeof(x)) > 0)
803 TRACE("Shutting down thread...\n");
804 WaitForSingleObject(thread, INFINITE);
805 TRACE("Done\n");
809 return MMSYSERR_NOERROR;
812 static DWORD MIX_GetDevCaps(UINT wDevID, LPMIXERCAPS2W caps, DWORD_PTR parm2)
814 mixer *mmixer = MIX_GetMix(wDevID);
815 MIXERCAPS2W capsW;
817 if (!caps)
818 return MMSYSERR_INVALPARAM;
820 if (!mmixer)
821 return MMSYSERR_BADDEVICEID;
823 memset(&capsW, 0, sizeof(MIXERCAPS2W));
825 capsW.wMid = WINE_MIXER_MANUF_ID;
826 capsW.wPid = WINE_MIXER_PRODUCT_ID;
827 capsW.vDriverVersion = WINE_MIXER_VERSION;
829 lstrcpynW(capsW.szPname, mmixer->mixername, sizeof(capsW.szPname)/sizeof(WCHAR));
830 capsW.cDestinations = mmixer->dests;
831 memcpy(caps, &capsW, min(parm2, sizeof(capsW)));
832 return MMSYSERR_NOERROR;
835 /* convert win32 volume to alsa volume, and vice versa */
836 static INT normalized(INT value, INT prevmax, INT nextmax)
838 int ret = MulDiv(value, nextmax, prevmax);
840 /* Have to stay in range */
841 TRACE("%d/%d -> %d/%d\n", value, prevmax, ret, nextmax);
842 if (ret > nextmax)
843 ret = nextmax;
844 else if (ret < 0)
845 ret = 0;
847 return ret;
850 /* get amount of sources for dest */
851 static int getsrccntfromchan(mixer *mmixer, int dad)
853 int i, j=0;
855 for (i=0; i<mmixer->chans; ++i)
856 if (i != dad && mmixer->lines[i].dst == dad)
858 ++j;
860 if (!j)
861 FIXME("No src found for %i (%s)?\n", dad, debugstr_w(mmixer->lines[dad].name));
862 return j;
865 /* find lineid for source 'num' with dest 'dad' */
866 static int getsrclinefromchan(mixer *mmixer, int dad, int num)
868 int i, j=0;
869 for (i=0; i<mmixer->chans; ++i)
870 if (i != dad && mmixer->lines[i].dst == dad)
872 if (num == j)
873 return i;
874 ++j;
876 WARN("No src found for src %i from dest %i\n", num, dad);
877 return 0;
880 /* get the source number belonging to line */
881 static int getsrcfromline(mixer *mmixer, int line)
883 int i, j=0, dad = mmixer->lines[line].dst;
885 for (i=0; i<mmixer->chans; ++i)
886 if (i != dad && mmixer->lines[i].dst == dad)
888 if (line == i)
889 return j;
890 ++j;
892 WARN("No src found for line %i with dad %i\n", line, dad);
893 return 0;
896 /* Get volume/muted/capture channel */
897 static DWORD MIX_GetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
899 mixer *mmixer = MIX_GetMix(wDevID);
900 DWORD ctrl;
901 DWORD line;
902 control *ct;
904 if (!mctrld)
905 return MMSYSERR_INVALPARAM;
907 ctrl = mctrld->dwControlID;
908 line = ctrl/CONTROLSPERLINE;
910 if (mctrld->cbStruct != sizeof(*mctrld))
911 return MMSYSERR_INVALPARAM;
913 if (!mmixer)
914 return MMSYSERR_BADDEVICEID;
916 if (line >= mmixer->chans || !mmixer->controls[ctrl].enabled)
917 return MIXERR_INVALCONTROL;
919 ct = &mmixer->controls[ctrl];
921 flags &= MIXER_GETCONTROLDETAILSF_QUERYMASK;
923 switch (flags) {
924 case MIXER_GETCONTROLDETAILSF_VALUE:
925 TRACE("MIXER_GETCONTROLDETAILSF_VALUE (%d/%d)\n", ctrl, line);
926 switch (ct->c.dwControlType)
928 case MIXERCONTROL_CONTROLTYPE_VOLUME:
930 long min = 0, max = 0, vol = 0;
931 int chn;
932 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
933 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
935 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
937 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
938 return MMSYSERR_INVALPARAM;
941 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
943 mcdu = mctrld->paDetails;
945 if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
947 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
948 return MMSYSERR_INVALPARAM;
951 if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem)) {
952 snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
953 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
954 if (snd_mixer_selem_has_capture_channel(elem, chn))
956 snd_mixer_selem_get_capture_volume(elem, chn, &vol);
957 mcdu->dwValue = normalized(vol - min, max, 65535);
958 if (mctrld->cChannels == 1)
959 break;
960 ++mcdu;
962 } else {
963 snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
965 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
966 if (snd_mixer_selem_has_playback_channel(elem, chn))
968 snd_mixer_selem_get_playback_volume(elem, chn, &vol);
969 mcdu->dwValue = normalized(vol - min, max, 65535);
970 if (mctrld->cChannels == 1)
971 break;
972 ++mcdu;
976 return MMSYSERR_NOERROR;
979 case MIXERCONTROL_CONTROLTYPE_ONOFF:
980 case MIXERCONTROL_CONTROLTYPE_MUTE:
982 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
983 int chn, ival;
984 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
986 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
988 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
989 return MMSYSERR_INVALPARAM;
992 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
994 mcdb = mctrld->paDetails;
996 if (line == 1)
997 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
999 if (!snd_mixer_selem_has_capture_channel(elem, chn))
1000 continue;
1001 snd_mixer_selem_get_capture_switch(elem, chn, &ival);
1002 break;
1004 else
1005 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1007 if (!snd_mixer_selem_has_playback_channel(elem, chn))
1008 continue;
1009 snd_mixer_selem_get_playback_switch(elem, chn, &ival);
1010 break;
1013 if (chn > SND_MIXER_SCHN_LAST)
1015 TRACE("can't find active channel\n");
1016 return MMSYSERR_INVALPARAM; /* fixme: what's right error? */
1019 mcdb->fValue = !ival;
1020 TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
1021 return MMSYSERR_NOERROR;
1023 case MIXERCONTROL_CONTROLTYPE_MIXER:
1024 case MIXERCONTROL_CONTROLTYPE_MUX:
1026 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1027 int x, i=0, ival = 0, chn;
1029 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1031 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1032 return MMSYSERR_INVALPARAM;
1035 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1037 mcdb = mctrld->paDetails;
1039 for (x = 0; x<mmixer->chans; ++x)
1040 if (line != x && mmixer->lines[x].dst == line)
1042 ival = 0;
1043 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1045 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1046 continue;
1047 snd_mixer_selem_get_capture_switch(mmixer->lines[x].elem, chn, &ival);
1048 if (ival)
1049 break;
1051 if (i >= mctrld->u.cMultipleItems)
1053 TRACE("overflow\n");
1054 return MMSYSERR_INVALPARAM;
1056 TRACE("fVal[%i] = %sselected\n", i, (!ival ? "un" : ""));
1057 mcdb[i++].fValue = ival;
1059 break;
1061 default:
1063 FIXME("Unhandled controltype %s\n", getControlType(ct->c.dwControlType));
1064 return MMSYSERR_INVALPARAM;
1066 return MMSYSERR_NOERROR;
1068 case MIXER_GETCONTROLDETAILSF_LISTTEXT:
1069 TRACE("MIXER_GETCONTROLDETAILSF_LISTTEXT (%d)\n", ctrl);
1071 if (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX || ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER)
1073 LPMIXERCONTROLDETAILS_LISTTEXTW mcdlt = mctrld->paDetails;
1074 int i, j;
1076 for (i = j = 0; j < mmixer->chans; ++j)
1077 if (j != line && mmixer->lines[j].dst == line)
1079 if (i > mctrld->u.cMultipleItems)
1080 return MMSYSERR_INVALPARAM;
1081 mcdlt->dwParam1 = j;
1082 mcdlt->dwParam2 = mmixer->lines[j].component;
1083 lstrcpynW(mcdlt->szName, mmixer->lines[j].name, sizeof(mcdlt->szName) / sizeof(WCHAR));
1084 TRACE("Adding %i as %s\n", j, debugstr_w(mcdlt->szName));
1085 ++i; ++mcdlt;
1087 if (i < mctrld->u.cMultipleItems)
1088 return MMSYSERR_INVALPARAM;
1089 return MMSYSERR_NOERROR;
1091 FIXME ("Imagine this code being horribly broken and incomplete, introducing: reality\n");
1092 return MMSYSERR_INVALPARAM;
1094 default:
1095 WARN("Unknown flag (%08lx)\n", flags);
1096 return MMSYSERR_INVALPARAM;
1100 /* Set volume/capture channel/muted for control */
1101 static DWORD MIX_SetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
1103 mixer *mmixer = MIX_GetMix(wDevID);
1104 DWORD ctrl, line, i;
1105 control *ct;
1106 snd_mixer_elem_t * elem;
1108 if (!mctrld)
1109 return MMSYSERR_INVALPARAM;
1111 ctrl = mctrld->dwControlID;
1112 line = ctrl/CONTROLSPERLINE;
1114 if (mctrld->cbStruct != sizeof(*mctrld))
1116 WARN("Invalid size of mctrld %d\n", mctrld->cbStruct);
1117 return MMSYSERR_INVALPARAM;
1120 if (!mmixer)
1121 return MMSYSERR_BADDEVICEID;
1123 if (line >= mmixer->chans)
1125 WARN("Invalid line id: %d not in range of 0-%d\n", line, mmixer->chans-1);
1126 return MMSYSERR_INVALPARAM;
1129 if (!mmixer->controls[ctrl].enabled)
1131 WARN("Control %d not enabled\n", ctrl);
1132 return MIXERR_INVALCONTROL;
1135 ct = &mmixer->controls[ctrl];
1136 elem = mmixer->lines[line].elem;
1137 flags &= MIXER_SETCONTROLDETAILSF_QUERYMASK;
1139 switch (flags) {
1140 case MIXER_SETCONTROLDETAILSF_VALUE:
1141 TRACE("MIXER_SETCONTROLDETAILSF_VALUE (%d)\n", ctrl);
1142 break;
1144 default:
1145 WARN("Unknown flag (%08lx)\n", flags);
1146 return MMSYSERR_INVALPARAM;
1149 switch (ct->c.dwControlType)
1151 case MIXERCONTROL_CONTROLTYPE_VOLUME:
1153 long min = 0, max = 0;
1154 int chn;
1155 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
1156 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
1158 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
1160 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1161 return MMSYSERR_INVALPARAM;
1164 if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
1166 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
1167 return MMSYSERR_INVALPARAM;
1170 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1171 mcdu = mctrld->paDetails;
1173 for (chn=0; chn<mctrld->cChannels;++chn)
1175 TRACE("Chan %d value %d\n", chn, mcdu[chn].dwValue);
1178 /* There isn't always a capture volume, so in that case change playback volume */
1179 if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem))
1181 snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
1183 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1184 if (snd_mixer_selem_has_capture_channel(elem, chn))
1186 snd_mixer_selem_set_capture_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
1187 if (mctrld->cChannels != 1)
1188 mcdu++;
1191 else
1193 snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
1195 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1196 if (snd_mixer_selem_has_playback_channel(elem, chn))
1198 snd_mixer_selem_set_playback_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
1199 if (mctrld->cChannels != 1)
1200 mcdu++;
1204 break;
1206 case MIXERCONTROL_CONTROLTYPE_MUTE:
1207 case MIXERCONTROL_CONTROLTYPE_ONOFF:
1209 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1211 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1213 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1214 return MMSYSERR_INVALPARAM;
1217 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1219 mcdb = mctrld->paDetails;
1220 if (line == 1) /* Mute/unmute capturing */
1221 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
1223 if (snd_mixer_selem_has_capture_channel(elem, i))
1224 snd_mixer_selem_set_capture_switch(elem, i, !mcdb->fValue);
1226 else
1227 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
1228 if (snd_mixer_selem_has_playback_channel(elem, i))
1229 snd_mixer_selem_set_playback_switch(elem, i, !mcdb->fValue);
1230 break;
1233 case MIXERCONTROL_CONTROLTYPE_MIXER:
1234 case MIXERCONTROL_CONTROLTYPE_MUX:
1236 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1237 int x, i=0, chn;
1238 int didone = 0, canone = (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX);
1240 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1242 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1243 return MMSYSERR_INVALPARAM;
1246 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1247 mcdb = mctrld->paDetails;
1249 for (x=i=0; x < mmixer->chans; ++x)
1250 if (line != x && mmixer->lines[x].dst == line)
1252 TRACE("fVal[%i] (%s) = %i\n", i, debugstr_w(mmixer->lines[x].name), mcdb[i].fValue);
1253 if (i >= mctrld->u.cMultipleItems)
1255 TRACE("Too many items to fit, overflowing\n");
1256 return MIXERR_INVALVALUE;
1258 if (mcdb[i].fValue && canone && didone)
1260 TRACE("Nice try, but it's not going to work\n");
1261 elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
1262 return MIXERR_INVALVALUE;
1264 if (mcdb[i].fValue)
1265 didone = 1;
1266 ++i;
1269 if (canone && !didone)
1271 TRACE("Nice try, this is not going to work either\n");
1272 elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
1273 return MIXERR_INVALVALUE;
1276 for (x = i = 0; x<mmixer->chans; ++x)
1277 if (line != x && mmixer->lines[x].dst == line)
1279 if (mcdb[i].fValue)
1280 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1282 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1283 continue;
1284 snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
1286 ++i;
1289 /* If it's a MUX, it means that only 1 channel can be selected
1290 * and the other channels are unselected
1292 * For MIXER multiple sources are allowed, so unselect here
1294 if (canone)
1295 break;
1297 for (x = i = 0; x<mmixer->chans; ++x)
1298 if (line != x && mmixer->lines[x].dst == line)
1300 if (!mcdb[i].fValue)
1301 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1303 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1304 continue;
1305 snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
1307 ++i;
1309 break;
1311 default:
1312 FIXME("Unhandled type %s\n", getControlType(ct->c.dwControlType));
1313 return MMSYSERR_INVALPARAM;
1315 return MMSYSERR_NOERROR;
1318 /* Here we give info over the source/dest line given by dwSource+dwDest or dwDest, respectively
1319 * It is also possible that a line is found by componenttype or target type, latter is not implemented yet
1320 * Most important values returned in struct:
1321 * dwLineID
1322 * sz(Short)Name
1323 * line control count
1324 * amount of channels
1326 static DWORD MIX_GetLineInfo(UINT wDevID, LPMIXERLINEW Ml, DWORD_PTR flags)
1328 DWORD_PTR qf = flags & MIXER_GETLINEINFOF_QUERYMASK;
1329 mixer *mmixer = MIX_GetMix(wDevID);
1330 line *mline;
1331 int idx, i;
1333 if (!Ml)
1335 WARN("No Ml\n");
1336 return MMSYSERR_INVALPARAM;
1339 if (!mmixer)
1341 WARN("Device %u not found\n", wDevID);
1342 return MMSYSERR_BADDEVICEID;
1345 if (Ml->cbStruct != sizeof(*Ml))
1347 WARN("invalid parameter: Ml->cbStruct = %d\n", Ml->cbStruct);
1348 return MMSYSERR_INVALPARAM;
1351 Ml->dwUser = 0;
1352 Ml->fdwLine = MIXERLINE_LINEF_DISCONNECTED;
1353 switch (qf)
1355 case MIXER_GETLINEINFOF_COMPONENTTYPE:
1357 Ml->dwLineID = 0xFFFF;
1358 TRACE("Looking for componenttype %d/%x\n", Ml->dwComponentType, Ml->dwComponentType);
1359 for (idx = 0; idx < mmixer->chans; ++idx)
1360 if (mmixer->lines[idx].component == Ml->dwComponentType)
1362 Ml->dwLineID = idx;
1363 break;
1365 if (Ml->dwLineID == 0xFFFF)
1366 return MMSYSERR_KEYNOTFOUND;
1367 /* Now that we have lineid, fallback to lineid*/
1370 case MIXER_GETLINEINFOF_LINEID:
1371 if (Ml->dwLineID >= mmixer->chans)
1372 return MIXERR_INVALLINE;
1374 TRACE("MIXER_GETLINEINFOF_LINEID %d\n", Ml->dwLineID);
1375 Ml->dwDestination = mmixer->lines[Ml->dwLineID].dst;
1377 if (Ml->dwDestination != Ml->dwLineID)
1379 Ml->dwSource = getsrcfromline(mmixer, Ml->dwLineID);
1380 Ml->cConnections = 1;
1382 else
1384 Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
1385 Ml->dwSource = 0xFFFFFFFF;
1387 TRACE("Connections %d, source %d\n", Ml->cConnections, Ml->dwSource);
1388 break;
1390 case MIXER_GETLINEINFOF_DESTINATION:
1391 if (Ml->dwDestination >= mmixer->dests)
1393 WARN("dest %d out of bounds\n", Ml->dwDestination);
1394 return MIXERR_INVALLINE;
1397 Ml->dwLineID = Ml->dwDestination;
1398 Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
1399 Ml->dwSource = 0xFFFFFFFF;
1400 break;
1402 case MIXER_GETLINEINFOF_SOURCE:
1403 if (Ml->dwDestination >= mmixer->dests)
1405 WARN("dest %d for source out of bounds\n", Ml->dwDestination);
1406 return MIXERR_INVALLINE;
1409 if (Ml->dwSource >= getsrccntfromchan(mmixer, Ml->dwDestination))
1411 WARN("src %d out of bounds\n", Ml->dwSource);
1412 return MIXERR_INVALLINE;
1415 Ml->dwLineID = getsrclinefromchan(mmixer, Ml->dwDestination, Ml->dwSource);
1416 Ml->cConnections = 1;
1417 break;
1419 case MIXER_GETLINEINFOF_TARGETTYPE:
1420 FIXME("TODO: TARGETTYPE, stub\n");
1421 return MMSYSERR_INVALPARAM;
1423 default:
1424 FIXME("Unknown query flag: %08lx\n", qf);
1425 return MMSYSERR_INVALPARAM;
1428 Ml->fdwLine &= ~MIXERLINE_LINEF_DISCONNECTED;
1429 Ml->fdwLine |= MIXERLINE_LINEF_ACTIVE;
1430 if (Ml->dwLineID >= mmixer->dests)
1431 Ml->fdwLine |= MIXERLINE_LINEF_SOURCE;
1433 mline = &mmixer->lines[Ml->dwLineID];
1434 Ml->dwComponentType = mline->component;
1435 Ml->cChannels = mmixer->lines[Ml->dwLineID].chans;
1436 Ml->cControls = 0;
1438 for (i=CONTROLSPERLINE*Ml->dwLineID;i<CONTROLSPERLINE*(Ml->dwLineID+1); ++i)
1439 if (mmixer->controls[i].enabled)
1440 ++(Ml->cControls);
1442 lstrcpynW(Ml->szShortName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szShortName)/sizeof(WCHAR));
1443 lstrcpynW(Ml->szName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szName)/sizeof(WCHAR));
1444 if (mline->capt)
1445 Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
1446 else
1447 Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
1448 Ml->Target.dwDeviceID = 0xFFFFFFFF;
1449 Ml->Target.wMid = WINE_MIXER_MANUF_ID;
1450 Ml->Target.wPid = WINE_MIXER_PRODUCT_ID;
1451 Ml->Target.vDriverVersion = WINE_MIXER_VERSION;
1452 lstrcpynW(Ml->Target.szPname, mmixer->mixername, sizeof(Ml->Target.szPname)/sizeof(WCHAR));
1453 return MMSYSERR_NOERROR;
1456 /* Get the controls that belong to a certain line, either all or 1 */
1457 static DWORD MIX_GetLineControls(UINT wDevID, LPMIXERLINECONTROLSW mlc, DWORD_PTR flags)
1459 mixer *mmixer = MIX_GetMix(wDevID);
1460 int i,j = 0;
1461 DWORD ct;
1463 if (!mlc || mlc->cbStruct != sizeof(*mlc))
1465 WARN("Invalid mlc %p, cbStruct: %d\n", mlc, (!mlc ? -1 : mlc->cbStruct));
1466 return MMSYSERR_INVALPARAM;
1469 if (mlc->cbmxctrl != sizeof(MIXERCONTROLW))
1471 WARN("cbmxctrl %d\n", mlc->cbmxctrl);
1472 return MMSYSERR_INVALPARAM;
1475 if (!mmixer)
1476 return MMSYSERR_BADDEVICEID;
1478 flags &= MIXER_GETLINECONTROLSF_QUERYMASK;
1480 if (flags == MIXER_GETLINECONTROLSF_ONEBYID)
1481 mlc->dwLineID = mlc->u.dwControlID / CONTROLSPERLINE;
1483 if (mlc->dwLineID >= mmixer->chans)
1485 TRACE("Invalid dwLineID %d\n", mlc->dwLineID);
1486 return MIXERR_INVALLINE;
1489 switch (flags)
1491 case MIXER_GETLINECONTROLSF_ALL:
1492 TRACE("line=%08x MIXER_GETLINECONTROLSF_ALL (%d)\n", mlc->dwLineID, mlc->cControls);
1493 for (i = 0; i < CONTROLSPERLINE; ++i)
1494 if (mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].enabled)
1496 memcpy(&mlc->pamxctrl[j], &mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].c, sizeof(MIXERCONTROLW));
1497 TRACE("Added %s (%s)\n", debugstr_w(mlc->pamxctrl[j].szShortName), debugstr_w(mlc->pamxctrl[j].szName));
1498 ++j;
1499 if (j > mlc->cControls)
1501 WARN("invalid parameter\n");
1502 return MMSYSERR_INVALPARAM;
1506 if (!j || mlc->cControls > j)
1508 WARN("invalid parameter\n");
1509 return MMSYSERR_INVALPARAM;
1511 break;
1512 case MIXER_GETLINECONTROLSF_ONEBYID:
1513 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYID (%x)\n", mlc->dwLineID, mlc->u.dwControlID);
1515 if (!mmixer->controls[mlc->u.dwControlID].enabled)
1516 return MIXERR_INVALCONTROL;
1518 mlc->pamxctrl[0] = mmixer->controls[mlc->u.dwControlID].c;
1519 break;
1520 case MIXER_GETLINECONTROLSF_ONEBYTYPE:
1521 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYTYPE (%s)\n", mlc->dwLineID, getControlType(mlc->u.dwControlType));
1523 ct = mlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK;
1524 for (i = 0; i <= CONTROLSPERLINE; ++i)
1526 const int ofs = i+mlc->dwLineID*CONTROLSPERLINE;
1527 if (i == CONTROLSPERLINE)
1529 WARN("invalid parameter: control %s not found\n", getControlType(mlc->u.dwControlType));
1530 return MIXERR_INVALCONTROL;
1532 if (mmixer->controls[ofs].enabled && (mmixer->controls[ofs].c.dwControlType & MIXERCONTROL_CT_CLASS_MASK) == ct)
1534 mlc->pamxctrl[0] = mmixer->controls[ofs].c;
1535 break;
1538 break;
1539 default:
1540 FIXME("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
1541 return MMSYSERR_INVALPARAM;
1544 return MMSYSERR_NOERROR;
1547 #endif /*HAVE_ALSA*/
1549 /**************************************************************************
1550 * mxdMessage (WINEALSA.3)
1552 DWORD WINAPI ALSA_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
1553 DWORD_PTR dwParam1, DWORD_PTR dwParam2)
1555 #ifdef HAVE_ALSA
1556 DWORD ret;
1557 TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID, getMessage(wMsg),
1558 dwUser, dwParam1, dwParam2);
1560 switch (wMsg)
1562 case DRVM_INIT: ALSA_MixerInit(); ret = MMSYSERR_NOERROR; break;
1563 case DRVM_EXIT: ALSA_MixerExit(); ret = MMSYSERR_NOERROR; break;
1564 /* All taken care of by driver initialisation */
1565 /* Unimplemented, and not needed */
1566 case DRVM_ENABLE:
1567 case DRVM_DISABLE:
1568 ret = MMSYSERR_NOERROR; break;
1570 case MXDM_OPEN:
1571 ret = MIX_Open(wDevID, (LPMIXEROPENDESC) dwParam1, dwParam2); break;
1573 case MXDM_CLOSE:
1574 ret = MIX_Close(wDevID); break;
1576 case MXDM_GETDEVCAPS:
1577 ret = MIX_GetDevCaps(wDevID, (LPMIXERCAPS2W)dwParam1, dwParam2); break;
1579 case MXDM_GETLINEINFO:
1580 ret = MIX_GetLineInfo(wDevID, (LPMIXERLINEW)dwParam1, dwParam2); break;
1582 case MXDM_GETLINECONTROLS:
1583 ret = MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSW)dwParam1, dwParam2); break;
1585 case MXDM_GETCONTROLDETAILS:
1586 ret = MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
1588 case MXDM_SETCONTROLDETAILS:
1589 ret = MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
1591 case MXDM_GETNUMDEVS:
1592 ret = cards; break;
1594 default:
1595 WARN("unknown message %s!\n", getMessage(wMsg));
1596 return MMSYSERR_NOTSUPPORTED;
1599 TRACE("Returning %08X\n", ret);
1600 return ret;
1601 #else /*HAVE_ALSA*/
1602 TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
1604 return MMSYSERR_NOTENABLED;
1605 #endif /*HAVE_ALSA*/