comctl32/tests: Remove win9x hacks.
[wine.git] / dlls / winealsa.drv / mixer.c
blobe28361ce32c58be90c4b41342a0e9b60b65a34aa
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 "mmreg.h"
49 #include "dsound.h"
50 #include "dsdriver.h"
51 #include "mmsystem.h"
52 #include "alsa.h"
53 #include "wine/unicode.h"
54 #include "wine/debug.h"
56 WINE_DEFAULT_DEBUG_CHANNEL(mixer);
58 #define WINE_MIXER_MANUF_ID 0xAA
59 #define WINE_MIXER_PRODUCT_ID 0x55
60 #define WINE_MIXER_VERSION 0x0100
62 /* Generic notes:
63 * In windows it seems to be required for all controls to have a volume switch
64 * In alsa that's optional
66 * I assume for playback controls, that there is always a playback volume switch available
67 * Mute is optional
69 * For capture controls, it is needed that there is a capture switch and a volume switch,
70 * It doesn't matter whether it is a playback volume switch or a capture volume switch.
71 * The code will first try to get/adjust capture volume, if that fails it tries playback volume
72 * It is not pretty, but under my 3 test cards it seems that there is no other choice:
73 * Most capture controls don't have a capture volume setting
75 * MUX means that only capture source can be exclusively selected,
76 * MIXER means that multiple sources can be selected simultaneously.
79 static const char * getMessage(UINT uMsg)
81 #define MSG_TO_STR(x) case x: return #x;
82 switch (uMsg){
83 MSG_TO_STR(DRVM_INIT);
84 MSG_TO_STR(DRVM_EXIT);
85 MSG_TO_STR(DRVM_ENABLE);
86 MSG_TO_STR(DRVM_DISABLE);
87 MSG_TO_STR(MXDM_GETDEVCAPS);
88 MSG_TO_STR(MXDM_GETLINEINFO);
89 MSG_TO_STR(MXDM_GETNUMDEVS);
90 MSG_TO_STR(MXDM_OPEN);
91 MSG_TO_STR(MXDM_CLOSE);
92 MSG_TO_STR(MXDM_GETLINECONTROLS);
93 MSG_TO_STR(MXDM_GETCONTROLDETAILS);
94 MSG_TO_STR(MXDM_SETCONTROLDETAILS);
95 default: break;
97 #undef MSG_TO_STR
98 return wine_dbg_sprintf("UNKNOWN(%08x)", uMsg);
101 static const char * getControlType(DWORD dwControlType)
103 #define TYPE_TO_STR(x) case x: return #x;
104 switch (dwControlType) {
105 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM);
106 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER);
107 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER);
108 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER);
109 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER);
110 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN);
111 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF);
112 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE);
113 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO);
114 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS);
115 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH);
116 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST);
117 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON);
118 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS);
119 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED);
120 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED);
121 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT);
122 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER);
123 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN);
124 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN);
125 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER);
126 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME);
127 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS);
128 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE);
129 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER);
130 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT);
131 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX);
132 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT);
133 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER);
134 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME);
135 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME);
137 #undef TYPE_TO_STR
138 return wine_dbg_sprintf("UNKNOWN(%08x)", dwControlType);
141 /* A simple declaration of a line control
142 * These are each of the channels that show up
144 typedef struct line {
145 /* Name we present to outside world */
146 WCHAR name[MAXPNAMELEN];
148 DWORD component;
149 DWORD dst;
150 DWORD capt;
151 DWORD chans;
152 snd_mixer_elem_t *elem;
153 } line;
155 /* A control structure, with toggle enabled switch
156 * Control structures control volume, muted, which capture source
158 typedef struct control {
159 BOOL enabled;
160 MIXERCONTROLW c;
161 } control;
163 /* Mixer device */
164 typedef struct mixer
166 snd_mixer_t *mix;
167 WCHAR mixername[MAXPNAMELEN];
169 int chans, dests;
170 LPDRVCALLBACK callback;
171 DWORD_PTR callbackpriv;
172 HDRVR hmx;
174 line *lines;
175 control *controls;
176 } mixer;
178 #define MAX_MIXERS 32
179 #define CONTROLSPERLINE 3
180 #define OFS_MUTE 2
181 #define OFS_MUX 1
183 static int cards = 0;
184 static mixer mixdev[MAX_MIXERS];
185 static HANDLE thread;
186 static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask);
187 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam);
188 static CRITICAL_SECTION elem_crst;
189 static int msg_pipe[2];
190 static LONG refcnt;
192 /* found channel names in alsa lib, alsa api doesn't have another way for this
193 * map name -> componenttype, worst case we get a wrong componenttype which is
194 * mostly harmless
197 static const struct mixerlinetype {
198 const char *name; DWORD cmpt;
199 } converttable[] = {
200 { "Master", MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, },
201 { "Capture", MIXERLINE_COMPONENTTYPE_DST_WAVEIN, },
202 { "PCM", MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT, },
203 { "PC Speaker", MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER, },
204 { "Synth", MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER, },
205 { "Headphone", MIXERLINE_COMPONENTTYPE_DST_HEADPHONES, },
206 { "Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
207 { "Aux", MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED, },
208 { "CD", MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC, },
209 { "Line", MIXERLINE_COMPONENTTYPE_SRC_LINE, },
210 { "Phone", MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE, },
211 { "Digital", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
212 { "Front Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
215 /* Map name to MIXERLINE_COMPONENTTYPE_XXX */
216 static int getcomponenttype(const char *name)
218 int x;
219 for (x=0; x< sizeof(converttable)/sizeof(converttable[0]); ++x)
220 if (!strcasecmp(name, converttable[x].name))
222 TRACE("%d -> %s\n", x, name);
223 return converttable[x].cmpt;
225 WARN("Unknown mixer name %s, probably harmless\n", name);
226 return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
229 /* Is this control suited for showing up? */
230 static int blacklisted(snd_mixer_elem_t *elem)
232 const char *name = snd_mixer_selem_get_name(elem);
233 BOOL blisted = 0;
235 if (!snd_mixer_selem_has_playback_volume(elem) &&
236 !snd_mixer_selem_has_capture_volume(elem))
237 blisted = 1;
239 TRACE("%s: %x\n", name, blisted);
240 return blisted;
243 static void fillcontrols(mixer *mmixer)
245 int id;
246 for (id = 0; id < mmixer->chans; ++id)
248 line *mline = &mmixer->lines[id];
249 int ofs = CONTROLSPERLINE * id;
250 int x;
251 long min, max;
253 TRACE("Filling control %d\n", id);
254 if (!mline->elem)
255 break;
256 if (id == 1 && !mline->elem)
257 continue;
259 if (mline->capt && snd_mixer_selem_has_capture_volume(mline->elem))
260 snd_mixer_selem_get_capture_volume_range(mline->elem, &min, &max);
261 else
262 snd_mixer_selem_get_playback_volume_range(mline->elem, &min, &max);
264 /* (!snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) */
265 /* Volume, always enabled by definition of blacklisted channels */
266 mmixer->controls[ofs].enabled = 1;
267 mmixer->controls[ofs].c.cbStruct = sizeof(mmixer->controls[ofs].c);
268 mmixer->controls[ofs].c.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
269 mmixer->controls[ofs].c.dwControlID = ofs;
270 mmixer->controls[ofs].c.Bounds.s1.dwMinimum = 0;
271 mmixer->controls[ofs].c.Bounds.s1.dwMaximum = 65535;
272 mmixer->controls[ofs].c.Metrics.cSteps = 65536/(max-min);
274 if ((id == 1 && snd_mixer_selem_has_capture_switch(mline->elem)) ||
275 (!mline->capt && snd_mixer_selem_has_playback_switch(mline->elem)))
276 { /* MUTE button optional, main capture channel should have one too */
277 mmixer->controls[ofs+OFS_MUTE].enabled = 1;
278 mmixer->controls[ofs+OFS_MUTE].c.cbStruct = sizeof(mmixer->controls[ofs].c);
279 mmixer->controls[ofs+OFS_MUTE].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
280 mmixer->controls[ofs+OFS_MUTE].c.dwControlID = ofs+OFS_MUTE;
281 mmixer->controls[ofs+OFS_MUTE].c.Bounds.s1.dwMaximum = 1;
284 if (mline->capt && snd_mixer_selem_has_capture_switch_exclusive(mline->elem))
285 mmixer->controls[CONTROLSPERLINE+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUX;
287 if (id == 1)
288 { /* Capture select, in case cMultipleItems is 0, it means capture is disabled anyway */
289 mmixer->controls[ofs+OFS_MUX].enabled = 1;
290 mmixer->controls[ofs+OFS_MUX].c.cbStruct = sizeof(mmixer->controls[ofs].c);
291 mmixer->controls[ofs+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MIXER;
292 mmixer->controls[ofs+OFS_MUX].c.dwControlID = ofs+OFS_MUX;
293 mmixer->controls[ofs+OFS_MUX].c.fdwControl = MIXERCONTROL_CONTROLF_MULTIPLE;
295 for (x = 0; x<mmixer->chans; ++x)
296 if (x != id && mmixer->lines[x].dst == id)
297 ++(mmixer->controls[ofs+OFS_MUX].c.cMultipleItems);
298 if (!mmixer->controls[ofs+OFS_MUX].c.cMultipleItems)
299 mmixer->controls[ofs+OFS_MUX].enabled = 0;
301 mmixer->controls[ofs+OFS_MUX].c.Bounds.s1.dwMaximum = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems - 1;
302 mmixer->controls[ofs+OFS_MUX].c.Metrics.cSteps = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems;
304 for (x=0; x<CONTROLSPERLINE; ++x)
306 lstrcpynW(mmixer->controls[ofs+x].c.szShortName, mline->name, sizeof(mmixer->controls[ofs+x].c.szShortName)/sizeof(WCHAR));
307 lstrcpynW(mmixer->controls[ofs+x].c.szName, mline->name, sizeof(mmixer->controls[ofs+x].c.szName)/sizeof(WCHAR));
312 /* get amount of channels for elem */
313 /* Officially we should keep capture/playback separated,
314 * but that's not going to work in the alsa api */
315 static int chans(mixer *mmixer, snd_mixer_elem_t * elem, DWORD capt)
317 int ret=0, chn;
319 if (capt && snd_mixer_selem_has_capture_volume(elem)) {
320 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
321 if (snd_mixer_selem_has_capture_channel(elem, chn))
322 ++ret;
323 } else {
324 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
325 if (snd_mixer_selem_has_playback_channel(elem, chn))
326 ++ret;
328 if (!ret)
329 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"));
330 return ret;
333 static void filllines(mixer *mmixer, snd_mixer_elem_t *mastelem, snd_mixer_elem_t *captelem, int capt)
335 snd_mixer_elem_t *elem;
336 line *mline = mmixer->lines;
338 if (mastelem) {
339 /* Master control */
340 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(mastelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
341 mline->component = getcomponenttype(snd_mixer_selem_get_name(mastelem));
342 mline->dst = 0;
343 mline->capt = 0;
344 mline->elem = mastelem;
345 mline->chans = chans(mmixer, mastelem, 0);
347 snd_mixer_elem_set_callback(mastelem, &elem_callback);
348 snd_mixer_elem_set_callback_private(mastelem, mmixer);
349 } else {
350 MultiByteToWideChar(CP_UNIXCP, 0, "Empty Master Element", -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
353 /* Capture control
354 * Note: since mmixer->dests = 1, it means only playback control is visible
355 * This makes sense, because if there are no capture sources capture control
356 * can't do anything and should be invisible */
358 /* Control 1 is reserved for capture even when not enabled */
359 ++mline;
360 if (capt)
362 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
363 mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem));
364 mline->dst = 1;
365 mline->capt = 1;
366 mline->elem = captelem;
367 mline->chans = chans(mmixer, captelem, 1);
369 snd_mixer_elem_set_callback(captelem, &elem_callback);
370 snd_mixer_elem_set_callback_private(captelem, mmixer);
373 for (elem = snd_mixer_first_elem(mmixer->mix); elem; elem = snd_mixer_elem_next(elem))
374 if (elem != mastelem && elem != captelem && !blacklisted(elem))
376 const char * name = snd_mixer_selem_get_name(elem);
377 DWORD comp = getcomponenttype(name);
379 if (snd_mixer_selem_has_playback_volume(elem) &&
380 (snd_mixer_selem_has_capture_volume(elem) || comp != MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE))
382 (++mline)->component = comp;
383 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
384 mline->capt = mline->dst = 0;
385 mline->elem = elem;
386 mline->chans = chans(mmixer, elem, 0);
388 else if (!capt)
389 continue;
391 if (capt && (snd_mixer_selem_has_capture_volume(elem) || comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE))
393 (++mline)->component = comp;
394 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
395 mline->capt = mline->dst = 1;
396 mline->elem = elem;
397 mline->chans = chans(mmixer, elem, 1);
400 snd_mixer_elem_set_callback(elem, &elem_callback);
401 snd_mixer_elem_set_callback_private(elem, mmixer);
405 static void filllines_no_master(mixer *mmixer, snd_mixer_elem_t *captelem, int capt)
407 line *mline = mmixer->lines;
409 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
410 mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem));
411 mline->dst = 0;
412 mline->capt = 1;
413 mline->elem = captelem;
414 mline->chans = chans(mmixer, captelem, 1);
416 snd_mixer_elem_set_callback(captelem, &elem_callback);
417 snd_mixer_elem_set_callback_private(captelem, mmixer);
420 /* Windows api wants to have a 'master' device to which all slaves are attached
421 * There are 2 ones in this code:
422 * - 'Master', fall back to 'Headphone' if unavailable, and if that's not available 'PCM'
423 * - 'Capture'
424 * Capture might not always be available, so should be prepared to be without if needed
427 static void ALSA_MixerInit(void)
429 int x, mixnum = 0;
430 snd_ctl_card_info_t *info;
432 info = HeapAlloc( GetProcessHeap(), 0, snd_ctl_card_info_sizeof());
433 for (x = 0; x < MAX_MIXERS; ++x)
435 int card, err, capcontrols = 0, total_elems = 0;
436 char cardind[6], cardname[10];
438 snd_ctl_t *ctl;
439 snd_mixer_elem_t *elem, *mastelem = NULL, *headelem = NULL, *captelem = NULL, *pcmelem = NULL, *lineelem = NULL, *micelem = NULL;
441 memset(info, 0, snd_ctl_card_info_sizeof());
442 memset(&mixdev[mixnum], 0, sizeof(*mixdev));
443 snprintf(cardind, sizeof(cardind), "%d", x);
444 card = snd_card_get_index(cardind);
445 if (card < 0)
446 continue;
448 snprintf(cardname, sizeof(cardname), "hw:%d", card);
450 err = snd_ctl_open(&ctl, cardname, 0);
451 if (err < 0)
453 WARN("Cannot open card: %s\n", snd_strerror(err));
454 continue;
457 err = snd_ctl_card_info(ctl, info);
458 if (err < 0)
460 WARN("Cannot get card info: %s\n", snd_strerror(err));
461 snd_ctl_close(ctl);
462 continue;
465 MultiByteToWideChar(CP_UNIXCP, 0, snd_ctl_card_info_get_name(info), -1, mixdev[mixnum].mixername, sizeof(mixdev[mixnum].mixername)/sizeof(WCHAR));
466 snd_ctl_close(ctl);
468 err = snd_mixer_open(&mixdev[mixnum].mix, 0);
469 if (err < 0)
471 WARN("Error occurred opening mixer: %s\n", snd_strerror(err));
472 continue;
475 err = snd_mixer_attach(mixdev[mixnum].mix, cardname);
476 if (err < 0)
477 goto eclose;
479 err = snd_mixer_selem_register(mixdev[mixnum].mix, NULL, NULL);
480 if (err < 0)
481 goto eclose;
483 err = snd_mixer_load(mixdev[mixnum].mix);
484 if (err < 0)
485 goto eclose;
487 /* First, lets see what's available..
488 * If there are multiple Master or Captures, all except 1 will be added as slaves
490 total_elems = snd_mixer_get_count(mixdev[mixnum].mix);
491 TRACE("Total elems: %d\n", total_elems);
493 for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
494 if (!strcasecmp(snd_mixer_selem_get_name(elem), "Master") && !mastelem)
496 mastelem = elem;
497 ++(mixdev[mixnum].chans);
499 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Capture") && !captelem)
500 captelem = elem;
501 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Mic") && !micelem && !mastelem && total_elems == 1)
502 /* this is what snd-usb-audio mics look like; just a Mic control and that's it.*/
503 micelem = elem;
504 else if (!blacklisted(elem))
506 DWORD comp = getcomponenttype(snd_mixer_selem_get_name(elem));
507 DWORD skip = 0;
509 /* Work around buggy drivers: Make this a capture control if the name is recognised as a microphone */
510 if (snd_mixer_selem_has_capture_volume(elem))
511 ++capcontrols;
512 else if (comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)
514 ++capcontrols;
515 skip = 1;
518 if (!skip && snd_mixer_selem_has_playback_volume(elem))
520 if (!strcasecmp(snd_mixer_selem_get_name(elem), "Headphone") && !headelem)
521 headelem = elem;
522 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "PCM") && !pcmelem)
523 pcmelem = elem;
524 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Line") && !lineelem)
525 lineelem = elem;
526 ++(mixdev[mixnum].chans);
530 /* Add dummy capture channel, wanted by Windows */
531 mixdev[mixnum].chans += 1;
533 /* If there is only 'Capture' and 'Master', this device is not worth it */
534 if (mixdev[mixnum].chans == 2)
536 WARN("No channels found, skipping device!\n");
537 goto close;
540 /* Master element can't have a capture control in this code, so
541 * if Headphone or PCM is promoted to master, unset its capture control */
542 if (headelem && !mastelem)
544 /* Using 'Headphone' as master device */
545 mastelem = headelem;
546 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
548 else if (pcmelem && !mastelem)
550 /* Use 'PCM' as master device */
551 mastelem = pcmelem;
552 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
554 else if (lineelem && !mastelem)
556 /* Use 'Line' as master device */
557 mastelem = lineelem;
558 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
560 else if (!mastelem && !captelem && !micelem)
562 /* If there is nothing sensible that can act as 'Master' control, something is wrong */
563 FIXME("No master control found on %s, disabling mixer\n", snd_ctl_card_info_get_name(info));
564 goto close;
567 if (!captelem || !capcontrols)
569 /* Can't enable capture, so disabling it
570 * Note: capture control will still exist because
571 * dwLineID 0 and 1 are reserved for Master and Capture
573 WARN("No use enabling capture part of mixer, capture control found: %s, amount of capture controls: %d\n",
574 (!captelem ? "no" : "yes"), capcontrols);
575 capcontrols = 0;
576 mixdev[mixnum].dests = 1;
578 else
580 mixdev[mixnum].chans += capcontrols;
581 mixdev[mixnum].dests = 2;
584 mixdev[mixnum].lines = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(line) * mixdev[mixnum].chans);
585 mixdev[mixnum].controls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(control) * CONTROLSPERLINE*mixdev[mixnum].chans);
586 err = -ENOMEM;
587 if (!mixdev[mixnum].lines || !mixdev[mixnum].controls)
588 goto close;
590 if (mastelem)
591 filllines(&mixdev[mixnum], mastelem, captelem, capcontrols);
592 else if (micelem)
593 filllines_no_master(&mixdev[mixnum], micelem, 1);
594 fillcontrols(&mixdev[mixnum]);
596 TRACE("%s: Amount of controls: %i/%i, name: %s\n", cardname, mixdev[mixnum].dests, mixdev[mixnum].chans, debugstr_w(mixdev[mixnum].mixername));
597 mixnum++;
598 continue;
600 eclose:
601 WARN("Error occurred initialising mixer: %s\n", snd_strerror(err));
602 close:
603 HeapFree(GetProcessHeap(), 0, mixdev[mixnum].lines);
604 HeapFree(GetProcessHeap(), 0, mixdev[mixnum].controls);
605 snd_mixer_close(mixdev[mixnum].mix);
607 cards = mixnum;
608 HeapFree( GetProcessHeap(), 0, info );
610 /* There is no trouble with already assigning callbacks without initialising critsect:
611 * Callbacks only occur when snd_mixer_handle_events is called (only happens in thread)
613 InitializeCriticalSection(&elem_crst);
614 elem_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_MIXER.elem_crst");
615 TRACE("\n");
618 static void ALSA_MixerExit(void)
620 int x;
622 if (refcnt)
624 WARN("Callback thread still alive, terminating uncleanly, refcnt: %d\n", refcnt);
625 /* Least we can do is making sure we're not in 'foreign' code */
626 EnterCriticalSection(&elem_crst);
627 TerminateThread(thread, 1);
628 refcnt = 0;
629 LeaveCriticalSection(&elem_crst);
632 TRACE("Cleaning up\n");
634 elem_crst.DebugInfo->Spare[0] = 0;
635 DeleteCriticalSection(&elem_crst);
636 for (x = 0; x < cards; ++x)
638 snd_mixer_close(mixdev[x].mix);
639 HeapFree(GetProcessHeap(), 0, mixdev[x].lines);
640 HeapFree(GetProcessHeap(), 0, mixdev[x].controls);
642 cards = 0;
645 static mixer* MIX_GetMix(UINT wDevID)
647 mixer *mmixer;
649 if (wDevID >= cards)
651 WARN("Invalid mixer id: %d\n", wDevID);
652 return NULL;
655 mmixer = &mixdev[wDevID];
656 return mmixer;
659 /* Since alsa doesn't tell what exactly changed, just assume all affected controls changed */
660 static int elem_callback(snd_mixer_elem_t *elem, unsigned int type)
662 mixer *mmixer = snd_mixer_elem_get_callback_private(elem);
663 int x;
664 BOOL captchanged = 0;
666 if (type != SND_CTL_EVENT_MASK_VALUE)
667 return 0;
669 assert(mmixer);
671 EnterCriticalSection(&elem_crst);
673 if (!mmixer->callback)
674 goto out;
676 for (x=0; x<mmixer->chans; ++x)
678 const int ofs = CONTROLSPERLINE*x;
679 if (elem != mmixer->lines[x].elem)
680 continue;
682 if (mmixer->lines[x].capt)
683 ++captchanged;
685 TRACE("Found changed control %s\n", debugstr_w(mmixer->lines[x].name));
686 mmixer->callback(mmixer->hmx, MM_MIXM_LINE_CHANGE, mmixer->callbackpriv, x, 0);
687 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs, 0);
689 if (mmixer->controls[ofs+OFS_MUTE].enabled)
690 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs+OFS_MUTE, 0);
692 if (captchanged)
693 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, CONTROLSPERLINE+OFS_MUX, 0);
695 out:
696 LeaveCriticalSection(&elem_crst);
698 return 0;
701 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam)
703 struct pollfd *pfds = NULL;
704 int x, y, err, mcnt, count = 1;
706 TRACE("%p\n", lParam);
708 for (x = 0; x < cards; ++x)
709 count += snd_mixer_poll_descriptors_count(mixdev[x].mix);
711 TRACE("Counted %d descriptors\n", count);
712 pfds = HeapAlloc(GetProcessHeap(), 0, count * sizeof(struct pollfd));
714 if (!pfds)
716 WARN("Out of memory\n");
717 goto die;
720 pfds[0].fd = msg_pipe[0];
721 pfds[0].events = POLLIN;
723 y = 1;
724 for (x = 0; x < cards; ++x)
725 y += snd_mixer_poll_descriptors(mixdev[x].mix, &pfds[y], count - y);
727 while ((err = poll(pfds, (unsigned int) count, -1)) >= 0 || errno == EINTR || errno == EAGAIN)
729 if (pfds[0].revents & POLLIN)
730 break;
732 mcnt = 1;
733 for (x = y = 0; x < cards; ++x)
735 int j, max = snd_mixer_poll_descriptors_count(mixdev[x].mix);
736 for (j = 0; j < max; ++j)
737 if (pfds[mcnt+j].revents)
739 y += snd_mixer_handle_events(mixdev[x].mix);
740 break;
742 mcnt += max;
744 if (y)
745 TRACE("Handled %d events\n", y);
748 die:
749 TRACE("Shutting down\n");
750 HeapFree(GetProcessHeap(), 0, pfds);
752 y = read(msg_pipe[0], &x, sizeof(x));
753 close(msg_pipe[1]);
754 close(msg_pipe[0]);
755 return 0;
758 static DWORD MIX_Open(UINT wDevID, LPMIXEROPENDESC desc, DWORD_PTR flags)
760 mixer *mmixer = MIX_GetMix(wDevID);
761 if (!mmixer)
762 return MMSYSERR_BADDEVICEID;
764 flags &= CALLBACK_TYPEMASK;
765 switch (flags)
767 case CALLBACK_NULL:
768 goto done;
770 case CALLBACK_FUNCTION:
771 break;
773 default:
774 FIXME("Unhandled callback type: %08lx\n", flags & CALLBACK_TYPEMASK);
775 return MIXERR_INVALVALUE;
778 mmixer->callback = (LPDRVCALLBACK)desc->dwCallback;
779 mmixer->callbackpriv = desc->dwInstance;
780 mmixer->hmx = (HDRVR)desc->hmx;
782 done:
783 if (InterlockedIncrement(&refcnt) == 1)
785 if (pipe(msg_pipe) >= 0)
787 thread = CreateThread(NULL, 0, ALSA_MixerPollThread, NULL, 0, NULL);
788 if (!thread)
790 close(msg_pipe[0]);
791 close(msg_pipe[1]);
792 msg_pipe[0] = msg_pipe[1] = -1;
795 else
796 msg_pipe[0] = msg_pipe[1] = -1;
799 return MMSYSERR_NOERROR;
802 static DWORD MIX_Close(UINT wDevID)
804 int x = 0;
805 mixer *mmixer = MIX_GetMix(wDevID);
806 if (!mmixer)
807 return MMSYSERR_BADDEVICEID;
809 EnterCriticalSection(&elem_crst);
810 mmixer->callback = 0;
811 LeaveCriticalSection(&elem_crst);
813 if (!InterlockedDecrement(&refcnt))
815 if (write(msg_pipe[1], &x, sizeof(x)) > 0)
817 TRACE("Shutting down thread...\n");
818 WaitForSingleObject(thread, INFINITE);
819 TRACE("Done\n");
823 return MMSYSERR_NOERROR;
826 static DWORD MIX_GetDevCaps(UINT wDevID, LPMIXERCAPS2W caps, DWORD_PTR parm2)
828 mixer *mmixer = MIX_GetMix(wDevID);
829 MIXERCAPS2W capsW;
831 if (!caps)
832 return MMSYSERR_INVALPARAM;
834 if (!mmixer)
835 return MMSYSERR_BADDEVICEID;
837 memset(&capsW, 0, sizeof(MIXERCAPS2W));
839 capsW.wMid = WINE_MIXER_MANUF_ID;
840 capsW.wPid = WINE_MIXER_PRODUCT_ID;
841 capsW.vDriverVersion = WINE_MIXER_VERSION;
843 lstrcpynW(capsW.szPname, mmixer->mixername, sizeof(capsW.szPname)/sizeof(WCHAR));
844 capsW.cDestinations = mmixer->dests;
845 memcpy(caps, &capsW, min(parm2, sizeof(capsW)));
846 return MMSYSERR_NOERROR;
849 /* convert win32 volume to alsa volume, and vice versa */
850 static INT normalized(INT value, INT prevmax, INT nextmax)
852 int ret = MulDiv(value, nextmax, prevmax);
854 /* Have to stay in range */
855 TRACE("%d/%d -> %d/%d\n", value, prevmax, ret, nextmax);
856 if (ret > nextmax)
857 ret = nextmax;
858 else if (ret < 0)
859 ret = 0;
861 return ret;
864 /* get amount of sources for dest */
865 static int getsrccntfromchan(mixer *mmixer, int dad)
867 int i, j=0;
869 for (i=0; i<mmixer->chans; ++i)
870 if (i != dad && mmixer->lines[i].dst == dad)
872 ++j;
874 if (!j)
875 FIXME("No src found for %i (%s)?\n", dad, debugstr_w(mmixer->lines[dad].name));
876 return j;
879 /* find lineid for source 'num' with dest 'dad' */
880 static int getsrclinefromchan(mixer *mmixer, int dad, int num)
882 int i, j=0;
883 for (i=0; i<mmixer->chans; ++i)
884 if (i != dad && mmixer->lines[i].dst == dad)
886 if (num == j)
887 return i;
888 ++j;
890 WARN("No src found for src %i from dest %i\n", num, dad);
891 return 0;
894 /* get the source number belonging to line */
895 static int getsrcfromline(mixer *mmixer, int line)
897 int i, j=0, dad = mmixer->lines[line].dst;
899 for (i=0; i<mmixer->chans; ++i)
900 if (i != dad && mmixer->lines[i].dst == dad)
902 if (line == i)
903 return j;
904 ++j;
906 WARN("No src found for line %i with dad %i\n", line, dad);
907 return 0;
910 /* Get volume/muted/capture channel */
911 static DWORD MIX_GetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
913 mixer *mmixer = MIX_GetMix(wDevID);
914 DWORD ctrl;
915 DWORD line;
916 control *ct;
918 if (!mctrld)
919 return MMSYSERR_INVALPARAM;
921 ctrl = mctrld->dwControlID;
922 line = ctrl/CONTROLSPERLINE;
924 if (mctrld->cbStruct != sizeof(*mctrld))
925 return MMSYSERR_INVALPARAM;
927 if (!mmixer)
928 return MMSYSERR_BADDEVICEID;
930 if (line >= mmixer->chans || !mmixer->controls[ctrl].enabled)
931 return MIXERR_INVALCONTROL;
933 ct = &mmixer->controls[ctrl];
935 flags &= MIXER_GETCONTROLDETAILSF_QUERYMASK;
937 switch (flags) {
938 case MIXER_GETCONTROLDETAILSF_VALUE:
939 TRACE("MIXER_GETCONTROLDETAILSF_VALUE (%d/%d)\n", ctrl, line);
940 switch (ct->c.dwControlType)
942 case MIXERCONTROL_CONTROLTYPE_VOLUME:
944 long min = 0, max = 0, vol = 0;
945 int chn;
946 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
947 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
949 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
951 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
952 return MMSYSERR_INVALPARAM;
955 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
957 mcdu = mctrld->paDetails;
959 if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
961 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
962 return MMSYSERR_INVALPARAM;
965 if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem)) {
966 snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
967 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
968 if (snd_mixer_selem_has_capture_channel(elem, chn))
970 snd_mixer_selem_get_capture_volume(elem, chn, &vol);
971 mcdu->dwValue = normalized(vol - min, max, 65535);
972 if (mctrld->cChannels == 1)
973 break;
974 ++mcdu;
976 } else {
977 snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
979 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
980 if (snd_mixer_selem_has_playback_channel(elem, chn))
982 snd_mixer_selem_get_playback_volume(elem, chn, &vol);
983 mcdu->dwValue = normalized(vol - min, max, 65535);
984 if (mctrld->cChannels == 1)
985 break;
986 ++mcdu;
990 return MMSYSERR_NOERROR;
993 case MIXERCONTROL_CONTROLTYPE_ONOFF:
994 case MIXERCONTROL_CONTROLTYPE_MUTE:
996 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
997 int chn, ival;
998 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
1000 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1002 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1003 return MMSYSERR_INVALPARAM;
1006 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1008 mcdb = mctrld->paDetails;
1010 if (line == 1)
1011 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1013 if (!snd_mixer_selem_has_capture_channel(elem, chn))
1014 continue;
1015 snd_mixer_selem_get_capture_switch(elem, chn, &ival);
1016 break;
1018 else
1019 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1021 if (!snd_mixer_selem_has_playback_channel(elem, chn))
1022 continue;
1023 snd_mixer_selem_get_playback_switch(elem, chn, &ival);
1024 break;
1027 if (chn > SND_MIXER_SCHN_LAST)
1029 TRACE("can't find active channel\n");
1030 return MMSYSERR_INVALPARAM; /* fixme: what's right error? */
1033 mcdb->fValue = !ival;
1034 TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
1035 return MMSYSERR_NOERROR;
1037 case MIXERCONTROL_CONTROLTYPE_MIXER:
1038 case MIXERCONTROL_CONTROLTYPE_MUX:
1040 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1041 int x, i=0, ival = 0, chn;
1043 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1045 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1046 return MMSYSERR_INVALPARAM;
1049 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1051 mcdb = mctrld->paDetails;
1053 for (x = 0; x<mmixer->chans; ++x)
1054 if (line != x && mmixer->lines[x].dst == line)
1056 ival = 0;
1057 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1059 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1060 continue;
1061 snd_mixer_selem_get_capture_switch(mmixer->lines[x].elem, chn, &ival);
1062 if (ival)
1063 break;
1065 if (i >= mctrld->u.cMultipleItems)
1067 TRACE("overflow\n");
1068 return MMSYSERR_INVALPARAM;
1070 TRACE("fVal[%i] = %sselected\n", i, (!ival ? "un" : ""));
1071 mcdb[i++].fValue = ival;
1073 break;
1075 default:
1077 FIXME("Unhandled controltype %s\n", getControlType(ct->c.dwControlType));
1078 return MMSYSERR_INVALPARAM;
1080 return MMSYSERR_NOERROR;
1082 case MIXER_GETCONTROLDETAILSF_LISTTEXT:
1083 TRACE("MIXER_GETCONTROLDETAILSF_LISTTEXT (%d)\n", ctrl);
1085 if (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX || ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER)
1087 LPMIXERCONTROLDETAILS_LISTTEXTW mcdlt = mctrld->paDetails;
1088 int i, j;
1090 for (i = j = 0; j < mmixer->chans; ++j)
1091 if (j != line && mmixer->lines[j].dst == line)
1093 if (i > mctrld->u.cMultipleItems)
1094 return MMSYSERR_INVALPARAM;
1095 mcdlt->dwParam1 = j;
1096 mcdlt->dwParam2 = mmixer->lines[j].component;
1097 lstrcpynW(mcdlt->szName, mmixer->lines[j].name, sizeof(mcdlt->szName) / sizeof(WCHAR));
1098 TRACE("Adding %i as %s\n", j, debugstr_w(mcdlt->szName));
1099 ++i; ++mcdlt;
1101 if (i < mctrld->u.cMultipleItems)
1102 return MMSYSERR_INVALPARAM;
1103 return MMSYSERR_NOERROR;
1105 FIXME ("Imagine this code being horribly broken and incomplete, introducing: reality\n");
1106 return MMSYSERR_INVALPARAM;
1108 default:
1109 WARN("Unknown flag (%08lx)\n", flags);
1110 return MMSYSERR_INVALPARAM;
1114 /* Set volume/capture channel/muted for control */
1115 static DWORD MIX_SetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
1117 mixer *mmixer = MIX_GetMix(wDevID);
1118 DWORD ctrl, line, i;
1119 control *ct;
1120 snd_mixer_elem_t * elem;
1122 if (!mctrld)
1123 return MMSYSERR_INVALPARAM;
1125 ctrl = mctrld->dwControlID;
1126 line = ctrl/CONTROLSPERLINE;
1128 if (mctrld->cbStruct != sizeof(*mctrld))
1130 WARN("Invalid size of mctrld %d\n", mctrld->cbStruct);
1131 return MMSYSERR_INVALPARAM;
1134 if (!mmixer)
1135 return MMSYSERR_BADDEVICEID;
1137 if (line >= mmixer->chans)
1139 WARN("Invalid line id: %d not in range of 0-%d\n", line, mmixer->chans-1);
1140 return MMSYSERR_INVALPARAM;
1143 if (!mmixer->controls[ctrl].enabled)
1145 WARN("Control %d not enabled\n", ctrl);
1146 return MIXERR_INVALCONTROL;
1149 ct = &mmixer->controls[ctrl];
1150 elem = mmixer->lines[line].elem;
1151 flags &= MIXER_SETCONTROLDETAILSF_QUERYMASK;
1153 switch (flags) {
1154 case MIXER_SETCONTROLDETAILSF_VALUE:
1155 TRACE("MIXER_SETCONTROLDETAILSF_VALUE (%d)\n", ctrl);
1156 break;
1158 default:
1159 WARN("Unknown flag (%08lx)\n", flags);
1160 return MMSYSERR_INVALPARAM;
1163 switch (ct->c.dwControlType)
1165 case MIXERCONTROL_CONTROLTYPE_VOLUME:
1167 long min = 0, max = 0;
1168 int chn;
1169 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
1170 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
1172 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
1174 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1175 return MMSYSERR_INVALPARAM;
1178 if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
1180 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
1181 return MMSYSERR_INVALPARAM;
1184 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1185 mcdu = mctrld->paDetails;
1187 for (chn=0; chn<mctrld->cChannels;++chn)
1189 TRACE("Chan %d value %d\n", chn, mcdu[chn].dwValue);
1192 /* There isn't always a capture volume, so in that case change playback volume */
1193 if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem))
1195 snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
1197 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1198 if (snd_mixer_selem_has_capture_channel(elem, chn))
1200 snd_mixer_selem_set_capture_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
1201 if (mctrld->cChannels != 1)
1202 mcdu++;
1205 else
1207 snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
1209 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1210 if (snd_mixer_selem_has_playback_channel(elem, chn))
1212 snd_mixer_selem_set_playback_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
1213 if (mctrld->cChannels != 1)
1214 mcdu++;
1218 break;
1220 case MIXERCONTROL_CONTROLTYPE_MUTE:
1221 case MIXERCONTROL_CONTROLTYPE_ONOFF:
1223 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1225 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1227 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1228 return MMSYSERR_INVALPARAM;
1231 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1233 mcdb = mctrld->paDetails;
1234 if (line == 1) /* Mute/unmute capturing */
1235 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
1237 if (snd_mixer_selem_has_capture_channel(elem, i))
1238 snd_mixer_selem_set_capture_switch(elem, i, !mcdb->fValue);
1240 else
1241 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
1242 if (snd_mixer_selem_has_playback_channel(elem, i))
1243 snd_mixer_selem_set_playback_switch(elem, i, !mcdb->fValue);
1244 break;
1247 case MIXERCONTROL_CONTROLTYPE_MIXER:
1248 case MIXERCONTROL_CONTROLTYPE_MUX:
1250 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1251 int x, i=0, chn;
1252 int didone = 0, canone = (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX);
1254 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1256 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1257 return MMSYSERR_INVALPARAM;
1260 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1261 mcdb = mctrld->paDetails;
1263 for (x=i=0; x < mmixer->chans; ++x)
1264 if (line != x && mmixer->lines[x].dst == line)
1266 TRACE("fVal[%i] (%s) = %i\n", i, debugstr_w(mmixer->lines[x].name), mcdb[i].fValue);
1267 if (i >= mctrld->u.cMultipleItems)
1269 TRACE("Too many items to fit, overflowing\n");
1270 return MIXERR_INVALVALUE;
1272 if (mcdb[i].fValue && canone && didone)
1274 TRACE("Nice try, but it's not going to work\n");
1275 elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
1276 return MIXERR_INVALVALUE;
1278 if (mcdb[i].fValue)
1279 didone = 1;
1280 ++i;
1283 if (canone && !didone)
1285 TRACE("Nice try, this is not going to work either\n");
1286 elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
1287 return MIXERR_INVALVALUE;
1290 for (x = i = 0; x<mmixer->chans; ++x)
1291 if (line != x && mmixer->lines[x].dst == line)
1293 if (mcdb[i].fValue)
1294 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1296 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1297 continue;
1298 snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
1300 ++i;
1303 /* If it's a MUX, it means that only 1 channel can be selected
1304 * and the other channels are unselected
1306 * For MIXER multiple sources are allowed, so unselect here
1308 if (canone)
1309 break;
1311 for (x = i = 0; x<mmixer->chans; ++x)
1312 if (line != x && mmixer->lines[x].dst == line)
1314 if (!mcdb[i].fValue)
1315 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1317 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1318 continue;
1319 snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
1321 ++i;
1323 break;
1325 default:
1326 FIXME("Unhandled type %s\n", getControlType(ct->c.dwControlType));
1327 return MMSYSERR_INVALPARAM;
1329 return MMSYSERR_NOERROR;
1332 /* Here we give info over the source/dest line given by dwSource+dwDest or dwDest, respectively
1333 * It is also possible that a line is found by componenttype or target type, latter is not implemented yet
1334 * Most important values returned in struct:
1335 * dwLineID
1336 * sz(Short)Name
1337 * line control count
1338 * amount of channels
1340 static DWORD MIX_GetLineInfo(UINT wDevID, LPMIXERLINEW Ml, DWORD_PTR flags)
1342 DWORD_PTR qf = flags & MIXER_GETLINEINFOF_QUERYMASK;
1343 mixer *mmixer = MIX_GetMix(wDevID);
1344 line *mline;
1345 int idx, i;
1347 if (!Ml)
1349 WARN("No Ml\n");
1350 return MMSYSERR_INVALPARAM;
1353 if (!mmixer)
1355 WARN("Device %u not found\n", wDevID);
1356 return MMSYSERR_BADDEVICEID;
1359 if (Ml->cbStruct != sizeof(*Ml))
1361 WARN("invalid parameter: Ml->cbStruct = %d\n", Ml->cbStruct);
1362 return MMSYSERR_INVALPARAM;
1365 Ml->dwUser = 0;
1366 Ml->fdwLine = MIXERLINE_LINEF_DISCONNECTED;
1367 switch (qf)
1369 case MIXER_GETLINEINFOF_COMPONENTTYPE:
1371 Ml->dwLineID = 0xFFFF;
1372 TRACE("Looking for componenttype %d/%x\n", Ml->dwComponentType, Ml->dwComponentType);
1373 for (idx = 0; idx < mmixer->chans; ++idx)
1374 if (mmixer->lines[idx].component == Ml->dwComponentType)
1376 Ml->dwLineID = idx;
1377 break;
1379 if (Ml->dwLineID == 0xFFFF)
1380 return MMSYSERR_KEYNOTFOUND;
1381 /* Now that we have lineid, fallback to lineid*/
1384 case MIXER_GETLINEINFOF_LINEID:
1385 if (Ml->dwLineID >= mmixer->chans)
1386 return MIXERR_INVALLINE;
1388 TRACE("MIXER_GETLINEINFOF_LINEID %d\n", Ml->dwLineID);
1389 Ml->dwDestination = mmixer->lines[Ml->dwLineID].dst;
1391 if (Ml->dwDestination != Ml->dwLineID)
1393 Ml->dwSource = getsrcfromline(mmixer, Ml->dwLineID);
1394 Ml->cConnections = 1;
1396 else
1398 Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
1399 Ml->dwSource = 0xFFFFFFFF;
1401 TRACE("Connections %d, source %d\n", Ml->cConnections, Ml->dwSource);
1402 break;
1404 case MIXER_GETLINEINFOF_DESTINATION:
1405 if (Ml->dwDestination >= mmixer->dests)
1407 WARN("dest %d out of bounds\n", Ml->dwDestination);
1408 return MIXERR_INVALLINE;
1411 Ml->dwLineID = Ml->dwDestination;
1412 Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
1413 Ml->dwSource = 0xFFFFFFFF;
1414 break;
1416 case MIXER_GETLINEINFOF_SOURCE:
1417 if (Ml->dwDestination >= mmixer->dests)
1419 WARN("dest %d for source out of bounds\n", Ml->dwDestination);
1420 return MIXERR_INVALLINE;
1423 if (Ml->dwSource >= getsrccntfromchan(mmixer, Ml->dwDestination))
1425 WARN("src %d out of bounds\n", Ml->dwSource);
1426 return MIXERR_INVALLINE;
1429 Ml->dwLineID = getsrclinefromchan(mmixer, Ml->dwDestination, Ml->dwSource);
1430 Ml->cConnections = 1;
1431 break;
1433 case MIXER_GETLINEINFOF_TARGETTYPE:
1434 FIXME("TODO: TARGETTYPE, stub\n");
1435 return MMSYSERR_INVALPARAM;
1437 default:
1438 FIXME("Unknown query flag: %08lx\n", qf);
1439 return MMSYSERR_INVALPARAM;
1442 Ml->fdwLine &= ~MIXERLINE_LINEF_DISCONNECTED;
1443 Ml->fdwLine |= MIXERLINE_LINEF_ACTIVE;
1444 if (Ml->dwLineID >= mmixer->dests)
1445 Ml->fdwLine |= MIXERLINE_LINEF_SOURCE;
1447 mline = &mmixer->lines[Ml->dwLineID];
1448 Ml->dwComponentType = mline->component;
1449 Ml->cChannels = mmixer->lines[Ml->dwLineID].chans;
1450 Ml->cControls = 0;
1452 for (i=CONTROLSPERLINE*Ml->dwLineID;i<CONTROLSPERLINE*(Ml->dwLineID+1); ++i)
1453 if (mmixer->controls[i].enabled)
1454 ++(Ml->cControls);
1456 lstrcpynW(Ml->szShortName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szShortName)/sizeof(WCHAR));
1457 lstrcpynW(Ml->szName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szName)/sizeof(WCHAR));
1458 if (mline->capt)
1459 Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
1460 else
1461 Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
1462 Ml->Target.dwDeviceID = 0xFFFFFFFF;
1463 Ml->Target.wMid = WINE_MIXER_MANUF_ID;
1464 Ml->Target.wPid = WINE_MIXER_PRODUCT_ID;
1465 Ml->Target.vDriverVersion = WINE_MIXER_VERSION;
1466 lstrcpynW(Ml->Target.szPname, mmixer->mixername, sizeof(Ml->Target.szPname)/sizeof(WCHAR));
1467 return MMSYSERR_NOERROR;
1470 /* Get the controls that belong to a certain line, either all or 1 */
1471 static DWORD MIX_GetLineControls(UINT wDevID, LPMIXERLINECONTROLSW mlc, DWORD_PTR flags)
1473 mixer *mmixer = MIX_GetMix(wDevID);
1474 int i,j = 0;
1475 DWORD ct;
1477 if (!mlc || mlc->cbStruct != sizeof(*mlc))
1479 WARN("Invalid mlc %p, cbStruct: %d\n", mlc, (!mlc ? -1 : mlc->cbStruct));
1480 return MMSYSERR_INVALPARAM;
1483 if (mlc->cbmxctrl != sizeof(MIXERCONTROLW))
1485 WARN("cbmxctrl %d\n", mlc->cbmxctrl);
1486 return MMSYSERR_INVALPARAM;
1489 if (!mmixer)
1490 return MMSYSERR_BADDEVICEID;
1492 flags &= MIXER_GETLINECONTROLSF_QUERYMASK;
1494 if (flags == MIXER_GETLINECONTROLSF_ONEBYID)
1495 mlc->dwLineID = mlc->u.dwControlID / CONTROLSPERLINE;
1497 if (mlc->dwLineID >= mmixer->chans)
1499 TRACE("Invalid dwLineID %d\n", mlc->dwLineID);
1500 return MIXERR_INVALLINE;
1503 switch (flags)
1505 case MIXER_GETLINECONTROLSF_ALL:
1506 TRACE("line=%08x MIXER_GETLINECONTROLSF_ALL (%d)\n", mlc->dwLineID, mlc->cControls);
1507 for (i = 0; i < CONTROLSPERLINE; ++i)
1508 if (mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].enabled)
1510 memcpy(&mlc->pamxctrl[j], &mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].c, sizeof(MIXERCONTROLW));
1511 TRACE("Added %s (%s)\n", debugstr_w(mlc->pamxctrl[j].szShortName), debugstr_w(mlc->pamxctrl[j].szName));
1512 ++j;
1513 if (j > mlc->cControls)
1515 WARN("invalid parameter\n");
1516 return MMSYSERR_INVALPARAM;
1520 if (!j || mlc->cControls > j)
1522 WARN("invalid parameter\n");
1523 return MMSYSERR_INVALPARAM;
1525 break;
1526 case MIXER_GETLINECONTROLSF_ONEBYID:
1527 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYID (%x)\n", mlc->dwLineID, mlc->u.dwControlID);
1529 if (!mmixer->controls[mlc->u.dwControlID].enabled)
1530 return MIXERR_INVALCONTROL;
1532 mlc->pamxctrl[0] = mmixer->controls[mlc->u.dwControlID].c;
1533 break;
1534 case MIXER_GETLINECONTROLSF_ONEBYTYPE:
1535 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYTYPE (%s)\n", mlc->dwLineID, getControlType(mlc->u.dwControlType));
1537 ct = mlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK;
1538 for (i = 0; i <= CONTROLSPERLINE; ++i)
1540 const int ofs = i+mlc->dwLineID*CONTROLSPERLINE;
1541 if (i == CONTROLSPERLINE)
1543 WARN("invalid parameter: control %s not found\n", getControlType(mlc->u.dwControlType));
1544 return MIXERR_INVALCONTROL;
1546 if (mmixer->controls[ofs].enabled && (mmixer->controls[ofs].c.dwControlType & MIXERCONTROL_CT_CLASS_MASK) == ct)
1548 mlc->pamxctrl[0] = mmixer->controls[ofs].c;
1549 break;
1552 break;
1553 default:
1554 FIXME("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
1555 return MMSYSERR_INVALPARAM;
1558 return MMSYSERR_NOERROR;
1561 /**************************************************************************
1562 * mxdMessage (WINEALSA.3)
1564 DWORD WINAPI ALSA_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
1565 DWORD_PTR dwParam1, DWORD_PTR dwParam2)
1567 DWORD ret;
1568 TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID, getMessage(wMsg),
1569 dwUser, dwParam1, dwParam2);
1571 switch (wMsg)
1573 case DRVM_INIT: ALSA_MixerInit(); ret = MMSYSERR_NOERROR; break;
1574 case DRVM_EXIT: ALSA_MixerExit(); ret = MMSYSERR_NOERROR; break;
1575 /* All taken care of by driver initialisation */
1576 /* Unimplemented, and not needed */
1577 case DRVM_ENABLE:
1578 case DRVM_DISABLE:
1579 ret = MMSYSERR_NOERROR; break;
1581 case MXDM_OPEN:
1582 ret = MIX_Open(wDevID, (LPMIXEROPENDESC) dwParam1, dwParam2); break;
1584 case MXDM_CLOSE:
1585 ret = MIX_Close(wDevID); break;
1587 case MXDM_GETDEVCAPS:
1588 ret = MIX_GetDevCaps(wDevID, (LPMIXERCAPS2W)dwParam1, dwParam2); break;
1590 case MXDM_GETLINEINFO:
1591 ret = MIX_GetLineInfo(wDevID, (LPMIXERLINEW)dwParam1, dwParam2); break;
1593 case MXDM_GETLINECONTROLS:
1594 ret = MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSW)dwParam1, dwParam2); break;
1596 case MXDM_GETCONTROLDETAILS:
1597 ret = MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
1599 case MXDM_SETCONTROLDETAILS:
1600 ret = MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
1602 case MXDM_GETNUMDEVS:
1603 ret = cards; break;
1605 default:
1606 WARN("unknown message %s!\n", getMessage(wMsg));
1607 return MMSYSERR_NOTSUPPORTED;
1610 TRACE("Returning %08X\n", ret);
1611 return ret;