new 818051de2c8769029049ce3d36c6b856f47496c9
[wine/hacks.git] / dlls / winealsa.drv / mixer.c
blob062b2ea8159dde85f7a1c8c3e98434db5dd0b70e
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 static char str[64];
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 sprintf(str, "UNKNOWN(%08x)", uMsg);
99 return str;
102 static const char * getControlType(DWORD dwControlType)
104 #define TYPE_TO_STR(x) case x: return #x;
105 switch (dwControlType) {
106 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM);
107 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER);
108 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER);
109 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER);
110 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER);
111 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN);
112 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF);
113 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE);
114 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO);
115 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS);
116 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH);
117 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST);
118 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON);
119 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS);
120 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED);
121 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED);
122 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT);
123 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER);
124 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN);
125 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN);
126 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER);
127 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME);
128 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS);
129 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE);
130 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER);
131 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT);
132 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX);
133 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT);
134 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER);
135 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME);
136 TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME);
138 #undef TYPE_TO_STR
139 return wine_dbg_sprintf("UNKNOWN(%08x)", dwControlType);
142 /* A simple declaration of a line control
143 * These are each of the channels that show up
145 typedef struct line {
146 /* Name we present to outside world */
147 WCHAR name[MAXPNAMELEN];
149 DWORD component;
150 DWORD dst;
151 DWORD capt;
152 DWORD chans;
153 snd_mixer_elem_t *elem;
154 } line;
156 /* A control structure, with toggle enabled switch
157 * Control structures control volume, muted, which capture source
159 typedef struct control {
160 BOOL enabled;
161 MIXERCONTROLW c;
162 } control;
164 /* Mixer device */
165 typedef struct mixer
167 snd_mixer_t *mix;
168 WCHAR mixername[MAXPNAMELEN];
170 int chans, dests;
171 LPDRVCALLBACK callback;
172 DWORD_PTR callbackpriv;
173 HDRVR hmx;
175 line *lines;
176 control *controls;
177 } mixer;
179 #define MAX_MIXERS 32
180 #define CONTROLSPERLINE 3
181 #define OFS_MUTE 2
182 #define OFS_MUX 1
184 static int cards = 0;
185 static mixer mixdev[MAX_MIXERS];
186 static HANDLE thread;
187 static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask);
188 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam);
189 static CRITICAL_SECTION elem_crst;
190 static int msg_pipe[2];
191 static LONG refcnt;
193 /* found channel names in alsa lib, alsa api doesn't have another way for this
194 * map name -> componenttype, worst case we get a wrong componenttype which is
195 * mostly harmless
198 static const struct mixerlinetype {
199 const char *name; DWORD cmpt;
200 } converttable[] = {
201 { "Master", MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, },
202 { "Capture", MIXERLINE_COMPONENTTYPE_DST_WAVEIN, },
203 { "PCM", MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT, },
204 { "PC Speaker", MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER, },
205 { "Synth", MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER, },
206 { "Headphone", MIXERLINE_COMPONENTTYPE_DST_HEADPHONES, },
207 { "Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
208 { "Aux", MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED, },
209 { "CD", MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC, },
210 { "Line", MIXERLINE_COMPONENTTYPE_SRC_LINE, },
211 { "Phone", MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE, },
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 !snd_mixer_selem_has_capture_switch(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 (id == 1 && !mline->elem)
255 continue;
257 if (mline->capt && snd_mixer_selem_has_capture_volume(mline->elem))
258 snd_mixer_selem_get_capture_volume_range(mline->elem, &min, &max);
259 else
260 snd_mixer_selem_get_playback_volume_range(mline->elem, &min, &max);
262 /* (!snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) */
263 /* Volume, always enabled by definition of blacklisted channels */
264 mmixer->controls[ofs].enabled = 1;
265 mmixer->controls[ofs].c.cbStruct = sizeof(mmixer->controls[ofs].c);
266 mmixer->controls[ofs].c.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
267 mmixer->controls[ofs].c.dwControlID = ofs;
268 mmixer->controls[ofs].c.Bounds.s1.dwMinimum = 0;
269 mmixer->controls[ofs].c.Bounds.s1.dwMaximum = 65535;
270 mmixer->controls[ofs].c.Metrics.cSteps = 65536/(max-min);
272 if ((id == 1 && snd_mixer_selem_has_capture_switch(mline->elem)) ||
273 (!mline->capt && snd_mixer_selem_has_playback_switch(mline->elem)))
274 { /* MUTE button optional, main capture channel should have one too */
275 mmixer->controls[ofs+OFS_MUTE].enabled = 1;
276 mmixer->controls[ofs+OFS_MUTE].c.cbStruct = sizeof(mmixer->controls[ofs].c);
277 mmixer->controls[ofs+OFS_MUTE].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
278 mmixer->controls[ofs+OFS_MUTE].c.dwControlID = ofs+OFS_MUTE;
279 mmixer->controls[ofs+OFS_MUTE].c.Bounds.s1.dwMaximum = 1;
282 if (mline->capt && snd_mixer_selem_has_capture_switch_exclusive(mline->elem))
283 mmixer->controls[CONTROLSPERLINE+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUX;
285 if (id == 1)
286 { /* Capture select, in case cMultipleItems is 0, it means capture is disabled anyway */
287 mmixer->controls[ofs+OFS_MUX].enabled = 1;
288 mmixer->controls[ofs+OFS_MUX].c.cbStruct = sizeof(mmixer->controls[ofs].c);
289 mmixer->controls[ofs+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MIXER;
290 mmixer->controls[ofs+OFS_MUX].c.dwControlID = ofs+OFS_MUX;
291 mmixer->controls[ofs+OFS_MUX].c.fdwControl = MIXERCONTROL_CONTROLF_MULTIPLE;
293 for (x = 0; x<mmixer->chans; ++x)
294 if (x != id && mmixer->lines[x].dst == id)
295 ++(mmixer->controls[ofs+OFS_MUX].c.cMultipleItems);
296 if (!mmixer->controls[ofs+OFS_MUX].c.cMultipleItems)
297 mmixer->controls[ofs+OFS_MUX].enabled = 0;
299 mmixer->controls[ofs+OFS_MUX].c.Bounds.s1.dwMaximum = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems - 1;
300 mmixer->controls[ofs+OFS_MUX].c.Metrics.cSteps = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems;
302 for (x=0; x<CONTROLSPERLINE; ++x)
304 lstrcpynW(mmixer->controls[ofs+x].c.szShortName, mline->name, sizeof(mmixer->controls[ofs+x].c.szShortName)/sizeof(WCHAR));
305 lstrcpynW(mmixer->controls[ofs+x].c.szName, mline->name, sizeof(mmixer->controls[ofs+x].c.szName)/sizeof(WCHAR));
310 /* get amount of channels for elem */
311 /* Officially we should keep capture/playback separated,
312 * but that's not going to work in the alsa api */
313 static int chans(mixer *mmixer, snd_mixer_elem_t * elem, DWORD capt)
315 int ret=0, chn;
317 if (capt && snd_mixer_selem_has_capture_volume(elem)) {
318 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
319 if (snd_mixer_selem_has_capture_channel(elem, chn))
320 ++ret;
321 } else {
322 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
323 if (snd_mixer_selem_has_playback_channel(elem, chn))
324 ++ret;
326 if (!ret)
327 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"));
328 return ret;
331 static void filllines(mixer *mmixer, snd_mixer_elem_t *mastelem, snd_mixer_elem_t *captelem, int capt)
333 snd_mixer_elem_t *elem;
334 line *mline = mmixer->lines;
336 /* Master control */
337 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(mastelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
338 mline->component = getcomponenttype(snd_mixer_selem_get_name(mastelem));
339 mline->dst = 0;
340 mline->capt = 0;
341 mline->elem = mastelem;
342 mline->chans = chans(mmixer, mastelem, 0);
344 snd_mixer_elem_set_callback(mastelem, &elem_callback);
345 snd_mixer_elem_set_callback_private(mastelem, mmixer);
347 /* Capture control
348 * Note: since mmixer->dests = 1, it means only playback control is visible
349 * This makes sense, because if there are no capture sources capture control
350 * can't do anything and should be invisible */
352 /* Control 1 is reserved for capture even when not enabled */
353 ++mline;
354 if (capt)
356 MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
357 mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem));
358 mline->dst = 1;
359 mline->capt = 1;
360 mline->elem = captelem;
361 mline->chans = chans(mmixer, captelem, 1);
363 snd_mixer_elem_set_callback(captelem, &elem_callback);
364 snd_mixer_elem_set_callback_private(captelem, mmixer);
367 for (elem = snd_mixer_first_elem(mmixer->mix); elem; elem = snd_mixer_elem_next(elem))
368 if (elem != mastelem && elem != captelem && !blacklisted(elem))
370 const char * name = snd_mixer_selem_get_name(elem);
371 DWORD comp = getcomponenttype(name);
373 if (snd_mixer_selem_has_playback_volume(elem))
375 (++mline)->component = comp;
376 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
377 mline->capt = mline->dst = 0;
378 mline->elem = elem;
379 mline->chans = chans(mmixer, elem, 0);
381 else if (!capt)
382 continue;
384 if (capt && snd_mixer_selem_has_capture_switch(elem))
386 (++mline)->component = comp;
387 MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
388 mline->capt = mline->dst = 1;
389 mline->elem = elem;
390 mline->chans = chans(mmixer, elem, 1);
393 snd_mixer_elem_set_callback(elem, &elem_callback);
394 snd_mixer_elem_set_callback_private(elem, mmixer);
398 /* Windows api wants to have a 'master' device to which all slaves are attached
399 * There are 2 ones in this code:
400 * - 'Master', fall back to 'Headphone' if unavailable, and if that's not available 'PCM'
401 * - 'Capture'
402 * Capture might not always be available, so should be prepared to be without if needed
405 static void ALSA_MixerInit(void)
407 int x, mixnum = 0;
409 for (x = 0; x < MAX_MIXERS; ++x)
411 int card, err, capcontrols = 0;
412 char cardind[6], cardname[10];
414 snd_ctl_t *ctl;
415 snd_mixer_elem_t *elem, *mastelem = NULL, *headelem = NULL, *captelem = NULL, *pcmelem = NULL;
416 snd_ctl_card_info_t *info = NULL;
417 snd_ctl_card_info_alloca(&info);
419 memset(&mixdev[mixnum], 0, sizeof(*mixdev));
420 snprintf(cardind, sizeof(cardind), "%d", x);
421 card = snd_card_get_index(cardind);
422 if (card < 0)
423 continue;
425 snprintf(cardname, sizeof(cardname), "hw:%d", card);
427 err = snd_ctl_open(&ctl, cardname, 0);
428 if (err < 0)
430 WARN("Cannot open card: %s\n", snd_strerror(err));
431 continue;
434 err = snd_ctl_card_info(ctl, info);
435 if (err < 0)
437 WARN("Cannot get card info: %s\n", snd_strerror(err));
438 snd_ctl_close(ctl);
439 continue;
442 MultiByteToWideChar(CP_UNIXCP, 0, snd_ctl_card_info_get_name(info), -1, mixdev[mixnum].mixername, sizeof(mixdev[mixnum].mixername)/sizeof(WCHAR));
443 snd_ctl_close(ctl);
445 err = snd_mixer_open(&mixdev[mixnum].mix, 0);
446 if (err < 0)
448 WARN("Error occurred opening mixer: %s\n", snd_strerror(err));
449 continue;
452 err = snd_mixer_attach(mixdev[mixnum].mix, cardname);
453 if (err < 0)
454 goto eclose;
456 err = snd_mixer_selem_register(mixdev[mixnum].mix, NULL, NULL);
457 if (err < 0)
458 goto eclose;
460 err = snd_mixer_load(mixdev[mixnum].mix);
461 if (err < 0)
462 goto eclose;
464 /* First, lets see what's available..
465 * If there are multiple Master or Captures, all except 1 will be added as slaves
467 for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
468 if (!strcasecmp(snd_mixer_selem_get_name(elem), "Master") && !mastelem)
469 mastelem = elem;
470 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Capture") && !captelem)
471 captelem = elem;
472 else if (!blacklisted(elem))
474 if (snd_mixer_selem_has_capture_switch(elem))
475 ++capcontrols;
476 if (snd_mixer_selem_has_playback_volume(elem))
478 if (!strcasecmp(snd_mixer_selem_get_name(elem), "Headphone") && !headelem)
479 headelem = elem;
480 else if (!strcasecmp(snd_mixer_selem_get_name(elem), "PCM") && !pcmelem)
481 pcmelem = elem;
482 else
483 ++(mixdev[mixnum].chans);
487 /* Add master channel, uncounted channels and an extra for capture */
488 mixdev[mixnum].chans += !!mastelem + !!headelem + !!pcmelem + 1;
490 /* If there is only 'Capture' and 'Master', this device is not worth it */
491 if (mixdev[mixnum].chans == 2)
493 WARN("No channels found, skipping device!\n");
494 goto close;
497 /* Master element can't have a capture control in this code, so
498 * if Headphone or PCM is promoted to master, unset its capture control */
499 if (headelem && !mastelem)
501 /* Using 'Headphone' as master device */
502 mastelem = headelem;
503 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
505 else if (pcmelem && !mastelem)
507 /* Use 'PCM' as master device */
508 mastelem = pcmelem;
509 capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
511 else if (!mastelem)
513 /* If there is nothing sensible that can act as 'Master' control, something is wrong */
514 FIXME("No master control found, disabling mixer\n");
515 goto close;
518 if (!captelem || !capcontrols)
520 /* Can't enable capture, so disabling it
521 * Note: capture control will still exist because
522 * dwLineID 0 and 1 are reserved for Master and Capture
524 WARN("No use enabling capture part of mixer, capture control found: %s, amount of capture controls: %d\n",
525 (!captelem ? "no" : "yes"), capcontrols);
526 capcontrols = 0;
527 mixdev[mixnum].dests = 1;
529 else
531 mixdev[mixnum].chans += capcontrols;
532 mixdev[mixnum].dests = 2;
535 mixdev[mixnum].lines = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(line) * mixdev[mixnum].chans);
536 mixdev[mixnum].controls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(control) * CONTROLSPERLINE*mixdev[mixnum].chans);
537 err = -ENOMEM;
538 if (!mixdev[mixnum].lines || !mixdev[mixnum].controls)
539 goto close;
541 filllines(&mixdev[mixnum], mastelem, captelem, capcontrols);
542 fillcontrols(&mixdev[mixnum]);
544 TRACE("%s: Amount of controls: %i/%i, name: %s\n", cardname, mixdev[mixnum].dests, mixdev[mixnum].chans, debugstr_w(mixdev[mixnum].mixername));
545 mixnum++;
546 continue;
548 eclose:
549 WARN("Error occurred initialising mixer: %s\n", snd_strerror(err));
550 close:
551 HeapFree(GetProcessHeap(), 0, mixdev[mixnum].lines);
552 HeapFree(GetProcessHeap(), 0, mixdev[mixnum].controls);
553 snd_mixer_close(mixdev[mixnum].mix);
555 cards = mixnum;
557 /* There is no trouble with already assigning callbacks without initialising critsect:
558 * Callbacks only occur when snd_mixer_handle_events is called (only happens in thread)
560 InitializeCriticalSection(&elem_crst);
561 elem_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_MIXER.elem_crst");
562 TRACE("\n");
565 static void ALSA_MixerExit(void)
567 int x;
569 if (refcnt)
571 WARN("Callback thread still alive, terminating uncleanly, refcnt: %d\n", refcnt);
572 /* Least we can do is making sure we're not in 'foreign' code */
573 EnterCriticalSection(&elem_crst);
574 TerminateThread(thread, 1);
575 refcnt = 0;
578 TRACE("Cleaning up\n");
580 elem_crst.DebugInfo->Spare[0] = 0;
581 DeleteCriticalSection(&elem_crst);
582 for (x = 0; x < cards; ++x)
584 snd_mixer_close(mixdev[x].mix);
585 HeapFree(GetProcessHeap(), 0, mixdev[x].lines);
586 HeapFree(GetProcessHeap(), 0, mixdev[x].controls);
588 cards = 0;
591 static mixer* MIX_GetMix(UINT wDevID)
593 mixer *mmixer;
595 if (wDevID < 0 || wDevID >= cards)
597 WARN("Invalid mixer id: %d\n", wDevID);
598 return NULL;
601 mmixer = &mixdev[wDevID];
602 return mmixer;
605 /* Since alsa doesn't tell what exactly changed, just assume all affected controls changed */
606 static int elem_callback(snd_mixer_elem_t *elem, unsigned int type)
608 mixer *mmixer = snd_mixer_elem_get_callback_private(elem);
609 int x;
610 BOOL captchanged = 0;
612 if (type != SND_CTL_EVENT_MASK_VALUE)
613 return 0;
615 assert(mmixer);
617 EnterCriticalSection(&elem_crst);
619 if (!mmixer->callback)
620 goto out;
622 for (x=0; x<mmixer->chans; ++x)
624 const int ofs = CONTROLSPERLINE*x;
625 if (elem != mmixer->lines[x].elem)
626 continue;
628 if (mmixer->lines[x].capt)
629 ++captchanged;
631 TRACE("Found changed control %s\n", debugstr_w(mmixer->lines[x].name));
632 mmixer->callback(mmixer->hmx, MM_MIXM_LINE_CHANGE, mmixer->callbackpriv, x, 0);
633 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs, 0);
635 if (mmixer->controls[ofs+OFS_MUTE].enabled)
636 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs+OFS_MUTE, 0);
638 if (captchanged)
639 mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, CONTROLSPERLINE+OFS_MUX, 0);
641 out:
642 LeaveCriticalSection(&elem_crst);
644 return 0;
647 static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam)
649 struct pollfd *pfds = NULL;
650 int x, y, err, mcnt, count = 1;
652 TRACE("%p\n", lParam);
654 for (x = 0; x < cards; ++x)
655 count += snd_mixer_poll_descriptors_count(mixdev[x].mix);
657 TRACE("Counted %d descriptors\n", count);
658 pfds = HeapAlloc(GetProcessHeap(), 0, count * sizeof(struct pollfd));
660 if (!pfds)
662 WARN("Out of memory\n");
663 goto die;
666 pfds[0].fd = msg_pipe[0];
667 pfds[0].events = POLLIN;
669 y = 1;
670 for (x = 0; x < cards; ++x)
671 y += snd_mixer_poll_descriptors(mixdev[x].mix, &pfds[y], count - y);
673 while ((err = poll(pfds, (unsigned int) count, -1)) >= 0 || errno == EINTR || errno == EAGAIN)
675 if (pfds[0].revents & POLLIN)
676 break;
678 mcnt = 1;
679 for (x = y = 0; x < cards; ++x)
681 int j, max = snd_mixer_poll_descriptors_count(mixdev[x].mix);
682 for (j = 0; j < max; ++j)
683 if (pfds[mcnt+j].revents)
685 y += snd_mixer_handle_events(mixdev[x].mix);
686 break;
688 mcnt += max;
690 if (y)
691 TRACE("Handled %d events\n", y);
694 die:
695 TRACE("Shutting down\n");
696 HeapFree(GetProcessHeap(), 0, pfds);
698 y = read(msg_pipe[0], &x, sizeof(x));
699 close(msg_pipe[1]);
700 close(msg_pipe[0]);
701 return 0;
704 static DWORD MIX_Open(UINT wDevID, LPMIXEROPENDESC desc, DWORD_PTR flags)
706 mixer *mmixer = MIX_GetMix(wDevID);
707 if (!mmixer)
708 return MMSYSERR_BADDEVICEID;
710 flags &= CALLBACK_TYPEMASK;
711 switch (flags)
713 case CALLBACK_NULL:
714 goto done;
716 case CALLBACK_FUNCTION:
717 break;
719 default:
720 FIXME("Unhandled callback type: %08lx\n", flags & CALLBACK_TYPEMASK);
721 return MIXERR_INVALVALUE;
724 mmixer->callback = (LPDRVCALLBACK)desc->dwCallback;
725 mmixer->callbackpriv = desc->dwInstance;
726 mmixer->hmx = (HDRVR)desc->hmx;
728 done:
729 if (InterlockedIncrement(&refcnt) == 1)
731 if (pipe(msg_pipe) >= 0)
733 thread = CreateThread(NULL, 0, ALSA_MixerPollThread, NULL, 0, NULL);
734 if (!thread)
736 close(msg_pipe[0]);
737 close(msg_pipe[1]);
738 msg_pipe[0] = msg_pipe[1] = -1;
741 else
742 msg_pipe[0] = msg_pipe[1] = -1;
745 return MMSYSERR_NOERROR;
748 static DWORD MIX_Close(UINT wDevID)
750 int x;
751 mixer *mmixer = MIX_GetMix(wDevID);
752 if (!mmixer)
753 return MMSYSERR_BADDEVICEID;
755 EnterCriticalSection(&elem_crst);
756 mmixer->callback = 0;
757 LeaveCriticalSection(&elem_crst);
759 if (!InterlockedDecrement(&refcnt))
761 if (write(msg_pipe[1], &x, sizeof(x)) > 0)
763 TRACE("Shutting down thread...\n");
764 WaitForSingleObject(thread, INFINITE);
765 TRACE("Done\n");
769 return MMSYSERR_NOERROR;
772 static DWORD MIX_GetDevCaps(UINT wDevID, LPMIXERCAPS2W caps, DWORD_PTR parm2)
774 mixer *mmixer = MIX_GetMix(wDevID);
775 MIXERCAPS2W capsW;
777 if (!caps)
778 return MMSYSERR_INVALPARAM;
780 if (!mmixer)
781 return MMSYSERR_BADDEVICEID;
783 memset(&capsW, 0, sizeof(MIXERCAPS2W));
785 capsW.wMid = WINE_MIXER_MANUF_ID;
786 capsW.wPid = WINE_MIXER_PRODUCT_ID;
787 capsW.vDriverVersion = WINE_MIXER_VERSION;
789 lstrcpynW(capsW.szPname, mmixer->mixername, sizeof(capsW.szPname)/sizeof(WCHAR));
790 capsW.cDestinations = mmixer->dests;
791 memcpy(caps, &capsW, min(parm2, sizeof(capsW)));
792 return MMSYSERR_NOERROR;
795 /* convert win32 volume to alsa volume, and vice versa */
796 static INT normalized(INT value, INT prevmax, INT nextmax)
798 int ret = MulDiv(value, nextmax, prevmax);
800 /* Have to stay in range */
801 TRACE("%d/%d -> %d/%d\n", value, prevmax, ret, nextmax);
802 if (ret > nextmax)
803 ret = nextmax;
804 else if (ret < 0)
805 ret = 0;
807 return ret;
810 /* get amount of sources for dest */
811 static int getsrccntfromchan(mixer *mmixer, int dad)
813 int i, j=0;
815 for (i=0; i<mmixer->chans; ++i)
816 if (i != dad && mmixer->lines[i].dst == dad)
818 ++j;
820 if (!j)
821 FIXME("No src found for %i (%s)?\n", dad, debugstr_w(mmixer->lines[dad].name));
822 return j;
825 /* find lineid for source 'num' with dest 'dad' */
826 static int getsrclinefromchan(mixer *mmixer, int dad, int num)
828 int i, j=0;
829 for (i=0; i<mmixer->chans; ++i)
830 if (i != dad && mmixer->lines[i].dst == dad)
832 if (num == j)
833 return i;
834 ++j;
836 WARN("No src found for src %i from dest %i\n", num, dad);
837 return 0;
840 /* get the source number belonging to line */
841 static int getsrcfromline(mixer *mmixer, int line)
843 int i, j=0, dad = mmixer->lines[line].dst;
845 for (i=0; i<mmixer->chans; ++i)
846 if (i != dad && mmixer->lines[i].dst == dad)
848 if (line == i)
849 return j;
850 ++j;
852 WARN("No src found for line %i with dad %i\n", line, dad);
853 return 0;
856 /* Get volume/muted/capture channel */
857 static DWORD MIX_GetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
859 mixer *mmixer = MIX_GetMix(wDevID);
860 DWORD ctrl;
861 DWORD line;
862 control *ct;
864 if (!mctrld)
865 return MMSYSERR_INVALPARAM;
867 ctrl = mctrld->dwControlID;
868 line = ctrl/CONTROLSPERLINE;
870 if (mctrld->cbStruct != sizeof(*mctrld))
871 return MMSYSERR_INVALPARAM;
873 if (!mmixer)
874 return MMSYSERR_BADDEVICEID;
876 if (line < 0 || line >= mmixer->chans || !mmixer->controls[ctrl].enabled)
877 return MIXERR_INVALCONTROL;
879 ct = &mmixer->controls[ctrl];
881 flags &= MIXER_GETCONTROLDETAILSF_QUERYMASK;
883 switch (flags) {
884 case MIXER_GETCONTROLDETAILSF_VALUE:
885 TRACE("MIXER_GETCONTROLDETAILSF_VALUE (%d/%d)\n", ctrl, line);
886 switch (ct->c.dwControlType)
888 case MIXERCONTROL_CONTROLTYPE_VOLUME:
890 long min = 0, max = 0, vol = 0;
891 int chn;
892 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
893 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
895 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
897 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
898 return MMSYSERR_INVALPARAM;
901 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
903 mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)mctrld->paDetails;
905 if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
907 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
908 return MMSYSERR_INVALPARAM;
911 if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem)) {
912 snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
913 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
914 if (snd_mixer_selem_has_capture_channel(elem, chn))
916 snd_mixer_selem_get_capture_volume(elem, chn, &vol);
917 mcdu->dwValue = normalized(vol - min, max, 65535);
918 if (mctrld->cChannels == 1)
919 break;
920 ++mcdu;
922 } else {
923 snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
925 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
926 if (snd_mixer_selem_has_playback_channel(elem, chn))
928 snd_mixer_selem_get_playback_volume(elem, chn, &vol);
929 mcdu->dwValue = normalized(vol - min, max, 65535);
930 if (mctrld->cChannels == 1)
931 break;
932 ++mcdu;
936 return MMSYSERR_NOERROR;
939 case MIXERCONTROL_CONTROLTYPE_ONOFF:
940 case MIXERCONTROL_CONTROLTYPE_MUTE:
942 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
943 int chn, ival;
944 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
946 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
948 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
949 return MMSYSERR_INVALPARAM;
952 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
954 mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)mctrld->paDetails;
956 if (line == 1)
957 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
959 if (!snd_mixer_selem_has_capture_channel(elem, chn))
960 continue;
961 snd_mixer_selem_get_capture_switch(elem, chn, &ival);
962 break;
964 else
965 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
967 if (!snd_mixer_selem_has_playback_channel(elem, chn))
968 continue;
969 snd_mixer_selem_get_playback_switch(elem, chn, &ival);
970 break;
973 mcdb->fValue = !ival;
974 TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
975 return MMSYSERR_NOERROR;
977 case MIXERCONTROL_CONTROLTYPE_MIXER:
978 case MIXERCONTROL_CONTROLTYPE_MUX:
980 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
981 int x, i=0, ival = 0, chn;
983 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
985 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
986 return MMSYSERR_INVALPARAM;
989 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
991 mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)mctrld->paDetails;
993 for (x = 0; x<mmixer->chans; ++x)
994 if (line != x && mmixer->lines[x].dst == line)
996 ival = 0;
997 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
999 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1000 continue;
1001 snd_mixer_selem_get_capture_switch(mmixer->lines[x].elem, chn, &ival);
1002 if (ival)
1003 break;
1005 if (i >= mctrld->u.cMultipleItems)
1007 TRACE("overflow\n");
1008 return MMSYSERR_INVALPARAM;
1010 TRACE("fVal[%i] = %sselected\n", i, (!ival ? "un" : ""));
1011 mcdb[i++].fValue = ival;
1013 break;
1015 default:
1017 FIXME("Unhandled controltype %s\n", getControlType(ct->c.dwControlType));
1018 return MMSYSERR_INVALPARAM;
1020 return MMSYSERR_NOERROR;
1022 case MIXER_GETCONTROLDETAILSF_LISTTEXT:
1023 TRACE("MIXER_GETCONTROLDETAILSF_LISTTEXT (%d)\n", ctrl);
1025 if (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX || ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER)
1027 LPMIXERCONTROLDETAILS_LISTTEXTW mcdlt = (LPMIXERCONTROLDETAILS_LISTTEXTW)mctrld->paDetails;
1028 int i, j;
1030 for (i = j = 0; j < mmixer->chans; ++j)
1031 if (j != line && mmixer->lines[j].dst == line)
1033 if (i > mctrld->u.cMultipleItems)
1034 return MMSYSERR_INVALPARAM;
1035 mcdlt->dwParam1 = j;
1036 mcdlt->dwParam2 = mmixer->lines[j].component;
1037 lstrcpynW(mcdlt->szName, mmixer->lines[j].name, sizeof(mcdlt->szName) / sizeof(WCHAR));
1038 TRACE("Adding %i as %s\n", j, debugstr_w(mcdlt->szName));
1039 ++i; ++mcdlt;
1041 if (i < mctrld->u.cMultipleItems)
1042 return MMSYSERR_INVALPARAM;
1043 return MMSYSERR_NOERROR;
1045 FIXME ("Imagine this code being horribly broken and incomplete, introducing: reality\n");
1046 return MMSYSERR_INVALPARAM;
1048 default:
1049 WARN("Unknown flag (%08lx)\n", flags);
1050 return MMSYSERR_INVALPARAM;
1054 /* Set volume/capture channel/muted for control */
1055 static DWORD MIX_SetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
1057 mixer *mmixer = MIX_GetMix(wDevID);
1058 DWORD ctrl, line, i;
1059 control *ct;
1060 snd_mixer_elem_t * elem;
1062 if (!mctrld)
1063 return MMSYSERR_INVALPARAM;
1065 ctrl = mctrld->dwControlID;
1066 line = ctrl/CONTROLSPERLINE;
1068 if (mctrld->cbStruct != sizeof(*mctrld))
1070 WARN("Invalid size of mctrld %d\n", mctrld->cbStruct);
1071 return MMSYSERR_INVALPARAM;
1074 if (!mmixer)
1075 return MMSYSERR_BADDEVICEID;
1077 if (line < 0 || line >= mmixer->chans)
1079 WARN("Invalid line id: %d not in range of 0-%d\n", line, mmixer->chans-1);
1080 return MMSYSERR_INVALPARAM;
1083 if (!mmixer->controls[ctrl].enabled)
1085 WARN("Control %d not enabled\n", ctrl);
1086 return MIXERR_INVALCONTROL;
1089 ct = &mmixer->controls[ctrl];
1090 elem = mmixer->lines[line].elem;
1091 flags &= MIXER_SETCONTROLDETAILSF_QUERYMASK;
1093 switch (flags) {
1094 case MIXER_SETCONTROLDETAILSF_VALUE:
1095 TRACE("MIXER_SETCONTROLDETAILSF_VALUE (%d)\n", ctrl);
1096 break;
1098 default:
1099 WARN("Unknown flag (%08lx)\n", flags);
1100 return MMSYSERR_INVALPARAM;
1103 switch (ct->c.dwControlType)
1105 case MIXERCONTROL_CONTROLTYPE_VOLUME:
1107 long min = 0, max = 0;
1108 int chn;
1109 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
1110 snd_mixer_elem_t * elem = mmixer->lines[line].elem;
1112 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
1114 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1115 return MMSYSERR_INVALPARAM;
1118 if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
1120 WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
1121 return MMSYSERR_INVALPARAM;
1124 TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1125 mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)mctrld->paDetails;
1127 for (chn=0; chn<mctrld->cChannels;++chn)
1129 TRACE("Chan %d value %d\n", chn, mcdu[chn].dwValue);
1132 /* There isn't always a capture volume, so in that case change playback volume */
1133 if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem))
1135 snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
1137 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1138 if (snd_mixer_selem_has_capture_channel(elem, chn))
1140 snd_mixer_selem_set_capture_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
1141 if (mctrld->cChannels != 1)
1142 mcdu++;
1145 else
1147 snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
1149 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1150 if (snd_mixer_selem_has_playback_channel(elem, chn))
1152 snd_mixer_selem_set_playback_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
1153 if (mctrld->cChannels != 1)
1154 mcdu++;
1158 break;
1160 case MIXERCONTROL_CONTROLTYPE_MUTE:
1161 case MIXERCONTROL_CONTROLTYPE_ONOFF:
1163 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1165 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1167 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1168 return MMSYSERR_INVALPARAM;
1171 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1173 mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)mctrld->paDetails;
1174 if (line == 1) /* Mute/unmute capturing */
1175 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
1177 if (snd_mixer_selem_has_capture_channel(elem, i))
1178 snd_mixer_selem_set_capture_switch(elem, i, !mcdb->fValue);
1180 else
1181 for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
1182 if (snd_mixer_selem_has_playback_channel(elem, i))
1183 snd_mixer_selem_set_playback_switch(elem, i, !mcdb->fValue);
1184 break;
1187 case MIXERCONTROL_CONTROLTYPE_MIXER:
1188 case MIXERCONTROL_CONTROLTYPE_MUX:
1190 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
1191 int x, i=0, chn;
1192 int didone = 0, canone = (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX);
1194 if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
1196 WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
1197 return MMSYSERR_INVALPARAM;
1200 TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
1201 mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)mctrld->paDetails;
1203 for (x=i=0; x < mmixer->chans; ++x)
1204 if (line != x && mmixer->lines[x].dst == line)
1206 TRACE("fVal[%i] (%s) = %i\n", i, debugstr_w(mmixer->lines[x].name), mcdb[i].fValue);
1207 if (i >= mctrld->u.cMultipleItems)
1209 TRACE("Too many items to fit, overflowing\n");
1210 return MIXERR_INVALVALUE;
1212 if (mcdb[i].fValue && canone && didone)
1214 TRACE("Nice try, but it's not going to work\n");
1215 elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
1216 return MIXERR_INVALVALUE;
1218 if (mcdb[i].fValue)
1219 didone = 1;
1220 ++i;
1223 if (canone && !didone)
1225 TRACE("Nice try, this is not going to work either\n");
1226 elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
1227 return MIXERR_INVALVALUE;
1230 for (x = i = 0; x<mmixer->chans; ++x)
1231 if (line != x && mmixer->lines[x].dst == line)
1233 if (mcdb[i].fValue)
1234 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1236 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1237 continue;
1238 snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
1240 ++i;
1243 /* If it's a MUX, it means that only 1 channel can be selected
1244 * and the other channels are unselected
1246 * For MIXER multiple sources are allowed, so unselect here
1248 if (canone)
1249 break;
1251 for (x = i = 0; x<mmixer->chans; ++x)
1252 if (line != x && mmixer->lines[x].dst == line)
1254 if (!mcdb[i].fValue)
1255 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
1257 if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
1258 continue;
1259 snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
1261 ++i;
1263 break;
1265 default:
1266 FIXME("Unhandled type %s\n", getControlType(ct->c.dwControlType));
1267 return MMSYSERR_INVALPARAM;
1269 return MMSYSERR_NOERROR;
1272 /* Here we give info over the source/dest line given by dwSource+dwDest or dwDest, respectively
1273 * It is also possible that a line is found by componenttype or target type, latter is not implemented yet
1274 * Most important values returned in struct:
1275 * dwLineID
1276 * sz(Short)Name
1277 * line control count
1278 * amount of channels
1280 static DWORD MIX_GetLineInfo(UINT wDevID, LPMIXERLINEW Ml, DWORD_PTR flags)
1282 DWORD_PTR qf = flags & MIXER_GETLINEINFOF_QUERYMASK;
1283 mixer *mmixer = MIX_GetMix(wDevID);
1284 line *mline;
1285 int idx, i;
1287 if (!Ml)
1289 WARN("No Ml\n");
1290 return MMSYSERR_INVALPARAM;
1293 if (!mmixer)
1295 WARN("Device %u not found\n", wDevID);
1296 return MMSYSERR_BADDEVICEID;
1299 if (Ml->cbStruct != sizeof(*Ml))
1301 WARN("invalid parameter: Ml->cbStruct = %d\n", Ml->cbStruct);
1302 return MMSYSERR_INVALPARAM;
1305 Ml->fdwLine = MIXERLINE_LINEF_ACTIVE;
1306 Ml->dwUser = 0;
1308 switch (qf)
1310 case MIXER_GETLINEINFOF_COMPONENTTYPE:
1312 Ml->dwLineID = 0xFFFF;
1313 for (idx = 0; idx < mmixer->chans; ++idx)
1314 if (mmixer->lines[idx].component == Ml->dwComponentType)
1316 Ml->dwLineID = idx;
1317 break;
1319 if (Ml->dwLineID == 0xFFFF)
1320 return MMSYSERR_KEYNOTFOUND;
1321 /* Now that we have lineid, fallback to lineid*/
1324 case MIXER_GETLINEINFOF_LINEID:
1325 if (Ml->dwLineID < 0 || Ml->dwLineID >= mmixer->chans)
1326 return MIXERR_INVALLINE;
1328 TRACE("MIXER_GETLINEINFOF_LINEID %d\n", Ml->dwLineID);
1329 Ml->dwDestination = mmixer->lines[Ml->dwLineID].dst;
1331 if (Ml->dwDestination != Ml->dwLineID)
1333 Ml->dwSource = getsrcfromline(mmixer, Ml->dwLineID);
1334 Ml->cConnections = 1;
1336 else
1338 Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
1339 Ml->dwSource = 0xFFFFFFFF;
1341 TRACE("Connections %d, source %d\n", Ml->cConnections, Ml->dwSource);
1342 break;
1344 case MIXER_GETLINEINFOF_DESTINATION:
1345 if (Ml->dwDestination < 0 || Ml->dwDestination >= mmixer->dests)
1347 WARN("dest %d out of bounds\n", Ml->dwDestination);
1348 return MIXERR_INVALLINE;
1351 Ml->dwLineID = Ml->dwDestination;
1352 Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
1353 Ml->dwSource = 0xFFFFFFFF;
1354 break;
1356 case MIXER_GETLINEINFOF_SOURCE:
1357 if (Ml->dwDestination < 0 || Ml->dwDestination >= mmixer->dests)
1359 WARN("dest %d for source out of bounds\n", Ml->dwDestination);
1360 return MIXERR_INVALLINE;
1363 if (Ml->dwSource < 0 || Ml->dwSource >= getsrccntfromchan(mmixer, Ml->dwDestination))
1365 WARN("src %d out of bounds\n", Ml->dwSource);
1366 return MIXERR_INVALLINE;
1369 Ml->dwLineID = getsrclinefromchan(mmixer, Ml->dwDestination, Ml->dwSource);
1370 Ml->cConnections = 1;
1371 break;
1373 case MIXER_GETLINEINFOF_TARGETTYPE:
1374 FIXME("TODO: TARGETTYPE, stub\n");
1375 return MMSYSERR_INVALPARAM;
1377 default:
1378 FIXME("Unknown query flag: %08lx\n", qf);
1379 return MMSYSERR_INVALPARAM;
1382 if (Ml->dwLineID >= mmixer->dests)
1383 Ml->fdwLine |= MIXERLINE_LINEF_SOURCE;
1385 mline = &mmixer->lines[Ml->dwLineID];
1386 Ml->dwComponentType = mline->component;
1387 Ml->cChannels = mmixer->lines[Ml->dwLineID].chans;
1388 Ml->cControls = 0;
1390 for (i=CONTROLSPERLINE*Ml->dwLineID;i<CONTROLSPERLINE*(Ml->dwLineID+1); ++i)
1391 if (mmixer->controls[i].enabled)
1392 ++(Ml->cControls);
1394 lstrcpynW(Ml->szShortName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szShortName)/sizeof(WCHAR));
1395 lstrcpynW(Ml->szName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szName)/sizeof(WCHAR));
1396 if (mline->capt)
1397 Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
1398 else
1399 Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
1400 Ml->Target.dwDeviceID = 0xFFFFFFFF;
1401 Ml->Target.wMid = WINE_MIXER_MANUF_ID;
1402 Ml->Target.wPid = WINE_MIXER_PRODUCT_ID;
1403 Ml->Target.vDriverVersion = WINE_MIXER_VERSION;
1404 lstrcpynW(Ml->Target.szPname, mmixer->mixername, sizeof(Ml->Target.szPname)/sizeof(WCHAR));
1405 return MMSYSERR_NOERROR;
1408 /* Get the controls that belong to a certain line, either all or 1 */
1409 static DWORD MIX_GetLineControls(UINT wDevID, LPMIXERLINECONTROLSW mlc, DWORD_PTR flags)
1411 mixer *mmixer = MIX_GetMix(wDevID);
1412 int i,j = 0;
1413 DWORD ct;
1415 if (!mlc || mlc->cbStruct != sizeof(*mlc))
1417 WARN("Invalid mlc %p, cbStruct: %d\n", mlc, (!mlc ? -1 : mlc->cbStruct));
1418 return MMSYSERR_INVALPARAM;
1421 if (mlc->cbmxctrl != sizeof(MIXERCONTROLW))
1423 WARN("cbmxctrl %d\n", mlc->cbmxctrl);
1424 return MMSYSERR_INVALPARAM;
1427 if (!mmixer)
1428 return MMSYSERR_BADDEVICEID;
1430 flags &= MIXER_GETLINECONTROLSF_QUERYMASK;
1432 if (flags == MIXER_GETLINECONTROLSF_ONEBYID)
1433 mlc->dwLineID = mlc->u.dwControlID / CONTROLSPERLINE;
1435 if (mlc->dwLineID < 0 || mlc->dwLineID >= mmixer->chans)
1437 TRACE("Invalid dwLineID %d\n", mlc->dwLineID);
1438 return MIXERR_INVALLINE;
1441 switch (flags)
1443 case MIXER_GETLINECONTROLSF_ALL:
1444 TRACE("line=%08x MIXER_GETLINECONTROLSF_ALL (%d)\n", mlc->dwLineID, mlc->cControls);
1445 for (i = 0; i < CONTROLSPERLINE; ++i)
1446 if (mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].enabled)
1448 memcpy(&mlc->pamxctrl[j], &mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].c, sizeof(MIXERCONTROLW));
1449 TRACE("Added %s (%s)\n", debugstr_w(mlc->pamxctrl[j].szShortName), debugstr_w(mlc->pamxctrl[j].szName));
1450 ++j;
1451 if (j > mlc->cControls)
1453 WARN("invalid parameter\n");
1454 return MMSYSERR_INVALPARAM;
1458 if (!j || mlc->cControls > j)
1460 WARN("invalid parameter\n");
1461 return MMSYSERR_INVALPARAM;
1463 break;
1464 case MIXER_GETLINECONTROLSF_ONEBYID:
1465 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYID (%x)\n", mlc->dwLineID, mlc->u.dwControlID);
1467 if (!mmixer->controls[mlc->u.dwControlID].enabled)
1468 return MIXERR_INVALCONTROL;
1470 mlc->pamxctrl[0] = mmixer->controls[mlc->u.dwControlID].c;
1471 break;
1472 case MIXER_GETLINECONTROLSF_ONEBYTYPE:
1473 TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYTYPE (%s)\n", mlc->dwLineID, getControlType(mlc->u.dwControlType));
1475 ct = mlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK;
1476 for (i = 0; i <= CONTROLSPERLINE; ++i)
1478 const int ofs = i+mlc->dwLineID*CONTROLSPERLINE;
1479 if (i == CONTROLSPERLINE)
1481 WARN("invalid parameter: control %s not found\n", getControlType(mlc->u.dwControlType));
1482 return MIXERR_INVALCONTROL;
1484 if (mmixer->controls[ofs].enabled && (mmixer->controls[ofs].c.dwControlType & MIXERCONTROL_CT_CLASS_MASK) == ct)
1486 mlc->pamxctrl[0] = mmixer->controls[ofs].c;
1487 break;
1490 break;
1491 default:
1492 FIXME("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
1493 return MMSYSERR_INVALPARAM;
1496 return MMSYSERR_NOERROR;
1499 #endif /*HAVE_ALSA*/
1501 /**************************************************************************
1502 * mxdMessage (WINEALSA.3)
1504 DWORD WINAPI ALSA_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
1505 DWORD_PTR dwParam1, DWORD_PTR dwParam2)
1507 #ifdef HAVE_ALSA
1508 DWORD ret;
1509 TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID, getMessage(wMsg),
1510 dwUser, dwParam1, dwParam2);
1512 switch (wMsg)
1514 case DRVM_INIT: ALSA_MixerInit(); ret = MMSYSERR_NOERROR; break;
1515 case DRVM_EXIT: ALSA_MixerExit(); ret = MMSYSERR_NOERROR; break;
1516 /* All taken care of by driver initialisation */
1517 /* Unimplemented, and not needed */
1518 case DRVM_ENABLE:
1519 case DRVM_DISABLE:
1520 ret = MMSYSERR_NOERROR; break;
1522 case MXDM_OPEN:
1523 ret = MIX_Open(wDevID, (LPMIXEROPENDESC) dwParam1, dwParam2); break;
1525 case MXDM_CLOSE:
1526 ret = MIX_Close(wDevID); break;
1528 case MXDM_GETDEVCAPS:
1529 ret = MIX_GetDevCaps(wDevID, (LPMIXERCAPS2W)dwParam1, dwParam2); break;
1531 case MXDM_GETLINEINFO:
1532 ret = MIX_GetLineInfo(wDevID, (LPMIXERLINEW)dwParam1, dwParam2); break;
1534 case MXDM_GETLINECONTROLS:
1535 ret = MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSW)dwParam1, dwParam2); break;
1537 case MXDM_GETCONTROLDETAILS:
1538 ret = MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
1540 case MXDM_SETCONTROLDETAILS:
1541 ret = MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
1543 case MXDM_GETNUMDEVS:
1544 ret = cards; break;
1546 default:
1547 WARN("unknown message %s!\n", getMessage(wMsg));
1548 return MMSYSERR_NOTSUPPORTED;
1551 TRACE("Returning %08X\n", ret);
1552 return ret;
1553 #else /*HAVE_ALSA*/
1554 TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
1556 return MMSYSERR_NOTENABLED;
1557 #endif /*HAVE_ALSA*/