Fixed a race condition on RPC worker thread creation, and a typo.
[wine] / dlls / winmm / wineoss / mixer.c
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
2
3 /*
4  * Sample MIXER Wine Driver for Linux
5  *
6  * Copyright    1997 Marcus Meissner
7  *              1999,2001 Eric Pouech
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23
24 #include "config.h"
25
26 #include <stdlib.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
38
39 #define NONAMELESSUNION
40 #define NONAMELESSSTRUCT
41 #include "windef.h"
42 #include "mmddk.h"
43 #include "oss.h"
44 #include "wine/debug.h"
45
46 WINE_DEFAULT_DEBUG_CHANNEL(mmaux);
47
48 #ifdef HAVE_OSS
49
50 #define WINE_MIXER_MANUF_ID             0xAA
51 #define WINE_MIXER_PRODUCT_ID           0x55
52 #define WINE_MIXER_VERSION              0x0100
53 #define WINE_MIXER_NAME                 "WINE OSS Mixer"
54
55 #define WINE_CHN_MASK(_x)               (1L << (_x))
56 #define WINE_CHN_SUPPORTS(_c, _x)       ((_c) & WINE_CHN_MASK(_x))
57 /* Bass and Treble are no longer in the mask as Windows does not handle them */
58 #define WINE_MIXER_MASK_SPEAKER         (WINE_CHN_MASK(SOUND_MIXER_SYNTH)  | \
59                                          WINE_CHN_MASK(SOUND_MIXER_PCM)    | \
60                                          WINE_CHN_MASK(SOUND_MIXER_LINE)   | \
61                                          WINE_CHN_MASK(SOUND_MIXER_MIC)    | \
62                                          WINE_CHN_MASK(SOUND_MIXER_CD)     )
63
64 #define WINE_MIXER_MASK_RECORD          (WINE_CHN_MASK(SOUND_MIXER_SYNTH)  | \
65                                          WINE_CHN_MASK(SOUND_MIXER_LINE)   | \
66                                          WINE_CHN_MASK(SOUND_MIXER_MIC)    | \
67                                          WINE_CHN_MASK(SOUND_MIXER_IMIX)   )
68
69 /* FIXME: the two following string arrays should be moved to a resource file in a string table */
70 /* if it's done, better use a struct to hold labels, name, and muted channel volume cache */
71 static char*    MIX_Labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
72 static char*    MIX_Names [SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
73
74 struct mixerCtrl
75 {
76     DWORD               dwLineID;
77     MIXERCONTROLA       ctrl;
78 };
79
80 struct mixer
81 {
82     const char*         name;
83     int                 volume[SOUND_MIXER_NRDEVICES];
84     int                 devMask;
85     int                 stereoMask;
86     int                 recMask;
87     BOOL                singleRecChannel;
88     struct mixerCtrl*   ctrl;
89     int                 numCtrl;
90 };
91
92 #define LINEID_DST      0xFFFF
93 #define LINEID_SPEAKER  0x0000
94 #define LINEID_RECORD   0x0001
95
96 static int              MIX_NumMixers;
97 static struct mixer     MIX_Mixers[1];
98
99 /**************************************************************************
100  *                              MIX_FillLineControls            [internal]
101  */
102 static void MIX_FillLineControls(struct mixer* mix, int c, DWORD lineID, DWORD dwType)
103 {
104     struct mixerCtrl*   mc = &mix->ctrl[c];
105     int                 j;
106
107     mc->dwLineID = lineID;
108     mc->ctrl.cbStruct = sizeof(MIXERCONTROLA);
109     mc->ctrl.dwControlID = c + 1;
110     mc->ctrl.dwControlType = dwType;
111
112     switch (dwType)
113     {
114     case MIXERCONTROL_CONTROLTYPE_VOLUME:
115         mc->ctrl.fdwControl = 0;
116         mc->ctrl.cMultipleItems = 0;
117         lstrcpynA(mc->ctrl.szShortName, "Vol", MIXER_SHORT_NAME_CHARS);
118         lstrcpynA(mc->ctrl.szName, "Volume", MIXER_LONG_NAME_CHARS);
119         memset(&mc->ctrl.Bounds, 0, sizeof(mc->ctrl.Bounds));
120         /* CONTROLTYPE_VOLUME uses the MIXER_CONTROLDETAILS_UNSIGNED struct,
121          * [0, 100] is the range supported by OSS
122          * whatever the min and max values are they must match
123          * conversions done in (Get|Set)ControlDetails to stay in [0, 100] range
124          */
125         mc->ctrl.Bounds.s1.dwMinimum = 0;
126         mc->ctrl.Bounds.s1.dwMaximum = 65535;
127         memset(&mc->ctrl.Metrics, 0, sizeof(mc->ctrl.Metrics));
128         break;
129     case MIXERCONTROL_CONTROLTYPE_MUTE:
130     case MIXERCONTROL_CONTROLTYPE_ONOFF:
131         mc->ctrl.fdwControl = 0;
132         mc->ctrl.cMultipleItems = 0;
133         lstrcpynA(mc->ctrl.szShortName, "Mute", MIXER_SHORT_NAME_CHARS);
134         lstrcpynA(mc->ctrl.szName, "Mute", MIXER_LONG_NAME_CHARS);
135         memset(&mc->ctrl.Bounds, 0, sizeof(mc->ctrl.Bounds));
136         mc->ctrl.Bounds.s1.dwMinimum = 0;
137         mc->ctrl.Bounds.s1.dwMaximum = 1;
138         memset(&mc->ctrl.Metrics, 0, sizeof(mc->ctrl.Metrics));
139         break;
140     case MIXERCONTROL_CONTROLTYPE_MUX:
141     case MIXERCONTROL_CONTROLTYPE_MIXER:
142         mc->ctrl.fdwControl = MIXERCONTROL_CONTROLF_MULTIPLE;
143         mc->ctrl.cMultipleItems = 0;
144         for (j = 0; j < SOUND_MIXER_NRDEVICES; j++)
145             if (WINE_CHN_SUPPORTS(mix->recMask, j))
146                 mc->ctrl.cMultipleItems++;
147         lstrcpynA(mc->ctrl.szShortName, "Mixer", MIXER_SHORT_NAME_CHARS);
148         lstrcpynA(mc->ctrl.szName, "Mixer", MIXER_LONG_NAME_CHARS);
149         memset(&mc->ctrl.Bounds, 0, sizeof(mc->ctrl.Bounds));
150         memset(&mc->ctrl.Metrics, 0, sizeof(mc->ctrl.Metrics));
151         break;
152
153     default:
154         FIXME("Internal error: unknown type: %08lx\n", dwType);
155     }
156     TRACE("ctrl[%2d]: typ=%08lx lin=%08lx\n", c + 1, dwType, lineID);
157 }
158
159 /******************************************************************
160  *              MIX_GetMixer
161  *
162  *
163  */
164 static struct mixer*    MIX_Get(WORD wDevID)
165 {
166     if (wDevID >= MIX_NumMixers || MIX_Mixers[wDevID].name == NULL) return NULL;
167     return &MIX_Mixers[wDevID];
168 }
169
170 /**************************************************************************
171  *                              MIX_Open                        [internal]
172  */
173 static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD flags)
174 {
175     int                 mixer, i, j;
176     unsigned            caps;
177     struct mixer*       mix;
178     DWORD               ret = MMSYSERR_NOERROR;
179
180     TRACE("(%04X, %p, %lu);\n", wDevID, lpMod, flags);
181
182     /* as we partly init the mixer with MIX_Open, we can allow null open decs */
183     /* EPP     if (lpMod == NULL) return MMSYSERR_INVALPARAM; */
184     /* anyway, it seems that WINMM/MMSYSTEM doesn't always open the mixer device before sending
185      * messages to it... it seems to be linked to all the equivalent of mixer identification
186      * (with a reference to a wave, midi.. handle
187      */
188     if (!(mix = MIX_Get(wDevID))) return MMSYSERR_BADDEVICEID;
189
190     if ((mixer = open(mix->name, O_RDWR)) < 0)
191     {
192         if (errno == ENODEV || errno == ENXIO)
193         {
194             /* no driver present */
195             return MMSYSERR_NODRIVER;
196         }
197         return MMSYSERR_ERROR;
198     }
199
200     if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &mix->devMask) == -1)
201     {
202         perror("ioctl mixer SOUND_MIXER_DEVMASK");
203         ret = MMSYSERR_ERROR;
204         goto error;
205     }
206     mix->devMask &= WINE_MIXER_MASK_SPEAKER;
207     if (mix->devMask == 0)
208     {
209         ret = MMSYSERR_NODRIVER;
210         goto error;
211     }
212
213     if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &mix->stereoMask) == -1)
214     {
215         perror("ioctl mixer SOUND_MIXER_STEREODEVS");
216         ret = MMSYSERR_ERROR;
217         goto error;
218     }
219     mix->stereoMask &= WINE_MIXER_MASK_SPEAKER;
220
221     if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &mix->recMask) == -1)
222     {
223         perror("ioctl mixer SOUND_MIXER_RECMASK");
224         ret = MMSYSERR_ERROR;
225         goto error;
226     }
227     mix->recMask &= WINE_MIXER_MASK_RECORD;
228     /* FIXME: we may need to support both rec lev & igain */
229     if (!WINE_CHN_SUPPORTS(mix->recMask, SOUND_MIXER_RECLEV))
230     {
231         WARN("The sound card doesn't support rec level\n");
232         if (WINE_CHN_SUPPORTS(mix->recMask, SOUND_MIXER_IGAIN))
233             WARN("but it does support IGain, please report\n");
234     }
235     if (ioctl(mixer, SOUND_MIXER_READ_CAPS, &caps) == -1)
236     {
237         perror("ioctl mixer SOUND_MIXER_READ_CAPS");
238         ret = MMSYSERR_ERROR;
239         goto error;
240     }
241     mix->singleRecChannel = caps & SOUND_CAP_EXCL_INPUT;
242     TRACE("dev=%04x rec=%04x stereo=%04x %s\n",
243           mix->devMask, mix->recMask, mix->stereoMask,
244           mix->singleRecChannel ? "single" : "multiple");
245     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
246     {
247         mix->volume[i] = -1;
248     }
249     mix->numCtrl = 4; /* dst lines... vol&mute on speakers, vol&onoff on rec */
250     /* FIXME: do we always have RECLEV on all cards ??? */
251     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
252     {
253         if (WINE_CHN_SUPPORTS(mix->devMask, i))
254             mix->numCtrl += 2; /* volume & mute */
255         if (WINE_CHN_SUPPORTS(mix->recMask, i))
256             mix->numCtrl += 2; /* volume & onoff */
257
258     }
259     if (!(mix->ctrl = HeapAlloc(GetProcessHeap(), 0, sizeof(mix->ctrl[0]) * mix->numCtrl)))
260     {
261         ret = MMSYSERR_NOMEM;
262         goto error;
263     }
264
265     j = 0;
266     MIX_FillLineControls(mix, j++, MAKELONG(0, LINEID_DST), MIXERCONTROL_CONTROLTYPE_VOLUME);
267     MIX_FillLineControls(mix, j++, MAKELONG(0, LINEID_DST), MIXERCONTROL_CONTROLTYPE_MUTE);
268     MIX_FillLineControls(mix, j++, MAKELONG(1, LINEID_DST),
269                          mix->singleRecChannel ?
270                             MIXERCONTROL_CONTROLTYPE_MUX : MIXERCONTROL_CONTROLTYPE_MIXER);
271     MIX_FillLineControls(mix, j++, MAKELONG(1, LINEID_DST), MIXERCONTROL_CONTROLTYPE_MUTE/*EPP*/);
272     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
273     {
274         if (WINE_CHN_SUPPORTS(mix->devMask, i))
275         {
276             MIX_FillLineControls(mix, j++, MAKELONG(LINEID_SPEAKER, i),
277                                  MIXERCONTROL_CONTROLTYPE_VOLUME);
278             MIX_FillLineControls(mix, j++, MAKELONG(LINEID_SPEAKER, i),
279                                  MIXERCONTROL_CONTROLTYPE_MUTE);
280         }
281     }
282     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
283     {
284         if (WINE_CHN_SUPPORTS(mix->recMask, i))
285         {
286             MIX_FillLineControls(mix, j++, MAKELONG(LINEID_RECORD, i),
287                                  MIXERCONTROL_CONTROLTYPE_VOLUME);
288             MIX_FillLineControls(mix, j++, MAKELONG(LINEID_RECORD, i),
289                                  MIXERCONTROL_CONTROLTYPE_MUTE/*EPP*/);
290         }
291     }
292     assert(j == mix->numCtrl);
293  error:
294     close(mixer);
295     return ret;
296 }
297
298 /**************************************************************************
299  *                              MIX_GetVal                      [internal]
300  */
301 static  BOOL    MIX_GetVal(struct mixer* mix, int chn, int* val)
302 {
303     int         mixer;
304     BOOL        ret = FALSE;
305
306     if ((mixer = open(mix->name, O_RDWR)) < 0)
307     {
308         /* FIXME: ENXIO => no mixer installed */
309         WARN("mixer device not available !\n");
310     }
311     else
312     {
313         if (ioctl(mixer, MIXER_READ(chn), val) >= 0)
314         {
315             TRACE("Reading volume %x on %d\n", *val, chn);
316             ret = TRUE;
317         }
318         close(mixer);
319     }
320     return ret;
321 }
322
323 /**************************************************************************
324  *                              MIX_SetVal                      [internal]
325  */
326 static  BOOL    MIX_SetVal(struct mixer* mix, int chn, int val)
327 {
328     int         mixer;
329     BOOL        ret = FALSE;
330
331     TRACE("Writing volume %x on %d\n", val, chn);
332
333     if ((mixer = open(mix->name, O_RDWR)) < 0)
334     {
335         /* FIXME: ENXIO => no mixer installed */
336         WARN("mixer device not available !\n");
337     }
338     else
339     {
340         if (ioctl(mixer, MIXER_WRITE(chn), &val) >= 0)
341         {
342             ret = TRUE;
343         }
344         close(mixer);
345     }
346     return ret;
347 }
348
349 /******************************************************************
350  *              MIX_GetRecSrc
351  *
352  *
353  */
354 static BOOL     MIX_GetRecSrc(struct mixer* mix, unsigned* mask)
355 {
356     int         mixer;
357     BOOL        ret = FALSE;
358
359     if ((mixer = open(mix->name, O_RDWR)) >= 0)
360     {
361         if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &mask) >= 0) ret = TRUE;
362         close(mixer);
363     }
364     return ret;
365 }
366
367 /******************************************************************
368  *              MIX_SetRecSrc
369  *
370  *
371  */
372 static BOOL     MIX_SetRecSrc(struct mixer* mix, unsigned mask)
373 {
374     int         mixer;
375     BOOL        ret = FALSE;
376
377     if ((mixer = open(mix->name, O_RDWR)) >= 0)
378     {
379         if (ioctl(mixer, SOUND_MIXER_WRITE_RECSRC, &mask) < 0)
380         {
381             ERR("Can't write new mixer settings\n");
382         }
383         else
384             ret = TRUE;
385         close(mixer);
386     }
387     return ret;
388 }
389
390 /**************************************************************************
391  *                              MIX_GetDevCaps                  [internal]
392  */
393 static DWORD MIX_GetDevCaps(WORD wDevID, LPMIXERCAPSA lpCaps, DWORD dwSize)
394 {
395     struct mixer*       mix;
396
397     TRACE("(%04X, %p, %lu);\n", wDevID, lpCaps, dwSize);
398
399     if (lpCaps == NULL) return MMSYSERR_INVALPARAM;
400     if (!(mix = MIX_Get(wDevID))) return MMSYSERR_BADDEVICEID;
401
402     lpCaps->wMid = WINE_MIXER_MANUF_ID;
403     lpCaps->wPid = WINE_MIXER_PRODUCT_ID;
404     lpCaps->vDriverVersion = WINE_MIXER_VERSION;
405     strcpy(lpCaps->szPname, WINE_MIXER_NAME);
406
407     lpCaps->cDestinations = 2; /* speakers & record */
408     lpCaps->fdwSupport = 0; /* No bits defined yet */
409
410     return MMSYSERR_NOERROR;
411 }
412
413 /**************************************************************************
414  *                              MIX_GetLineInfoDst      [internal]
415  */
416 static  DWORD   MIX_GetLineInfoDst(struct mixer* mix, LPMIXERLINEA lpMl, DWORD dst)
417 {
418     unsigned mask;
419     int j;
420
421     lpMl->dwDestination = dst;
422     switch (dst)
423     {
424     case 0:
425         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
426         mask = mix->devMask;
427         j = SOUND_MIXER_VOLUME;
428         break;
429     case 1:
430         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
431         mask = mix->recMask;
432         j = SOUND_MIXER_RECLEV;
433         break;
434     default:
435         FIXME("shouldn't happen\n");
436         return MMSYSERR_ERROR;
437     }
438     lpMl->dwSource = 0xFFFFFFFF;
439     lstrcpynA(lpMl->szShortName, MIX_Labels[j], MIXER_SHORT_NAME_CHARS);
440     lstrcpynA(lpMl->szName, MIX_Names[j], MIXER_LONG_NAME_CHARS);
441
442     /* we have all connections found in the MIX_DevMask */
443     lpMl->cConnections = 0;
444     for (j = 0; j < SOUND_MIXER_NRDEVICES; j++)
445         if (WINE_CHN_SUPPORTS(mask, j))
446             lpMl->cConnections++;
447     lpMl->cChannels = 1;
448     if (WINE_CHN_SUPPORTS(mix->stereoMask, lpMl->dwLineID))
449         lpMl->cChannels++;
450     lpMl->dwLineID = MAKELONG(dst, LINEID_DST);
451     lpMl->cControls = 0;
452     for (j = 0; j < mix->numCtrl; j++)
453         if (mix->ctrl[j].dwLineID == lpMl->dwLineID)
454             lpMl->cControls++;
455
456     return MMSYSERR_NOERROR;
457 }
458
459 /**************************************************************************
460  *                              MIX_GetLineInfoSrc      [internal]
461  */
462 static  DWORD   MIX_GetLineInfoSrc(struct mixer* mix, LPMIXERLINEA lpMl, DWORD idx, DWORD dst)
463 {
464     int         i, j;
465     unsigned    mask = (dst) ? mix->recMask : mix->devMask;
466
467     strcpy(lpMl->szShortName, MIX_Labels[idx]);
468     strcpy(lpMl->szName, MIX_Names[idx]);
469     lpMl->dwLineID = MAKELONG(dst, idx);
470     lpMl->dwDestination = dst;
471     lpMl->cConnections = 1;
472     lpMl->cControls = 0;
473     for (i = 0; i < mix->numCtrl; i++)
474         if (mix->ctrl[i].dwLineID == lpMl->dwLineID)
475             lpMl->cControls++;
476
477     switch (idx)
478     {
479     case SOUND_MIXER_SYNTH:
480         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER;
481         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
482         break;
483     case SOUND_MIXER_CD:
484         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;
485         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
486         break;
487     case SOUND_MIXER_LINE:
488         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_LINE;
489         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
490         break;
491     case SOUND_MIXER_MIC:
492         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
493         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
494         break;
495     case SOUND_MIXER_PCM:
496         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
497         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
498         break;
499     case SOUND_MIXER_IMIX:
500         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
501         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
502         break;
503     default:
504         WARN("Index %ld not handled.\n", idx);
505         return MIXERR_INVALLINE;
506     }
507     lpMl->cChannels = 1;
508     if (dst == 0 && WINE_CHN_SUPPORTS(mix->stereoMask, idx))
509         lpMl->cChannels++;
510     for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++)
511     {
512         if (WINE_CHN_SUPPORTS(mask, j))
513         {
514             if (j == idx) break;
515             i++;
516         }
517     }
518     lpMl->dwSource = i;
519     return MMSYSERR_NOERROR;
520 }
521
522 /******************************************************************
523  *              MIX_CheckLine
524  */
525 static BOOL MIX_CheckLine(DWORD lineID)
526 {
527     return ((HIWORD(lineID) < SOUND_MIXER_NRDEVICES && LOWORD(lineID) < 2) ||
528             (HIWORD(lineID) == LINEID_DST && LOWORD(lineID) < SOUND_MIXER_NRDEVICES));
529 }
530
531 /**************************************************************************
532  *                              MIX_GetLineInfo                 [internal]
533  */
534 static DWORD MIX_GetLineInfo(WORD wDevID, LPMIXERLINEA lpMl, DWORD fdwInfo)
535 {
536     int                 i, j;
537     DWORD               ret = MMSYSERR_NOERROR;
538     unsigned            mask;
539     struct mixer*       mix;
540
541     TRACE("(%04X, %p, %lu);\n", wDevID, lpMl, fdwInfo);
542
543     if (lpMl == NULL || lpMl->cbStruct != sizeof(*lpMl))
544         return MMSYSERR_INVALPARAM;
545     if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
546
547     /* FIXME: set all the variables correctly... the lines below
548      * are very wrong...
549      */
550     lpMl->fdwLine       = MIXERLINE_LINEF_ACTIVE;
551     lpMl->dwUser        = 0;
552
553     switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK)
554     {
555     case MIXER_GETLINEINFOF_DESTINATION:
556         TRACE("DESTINATION (%08lx)\n", lpMl->dwDestination);
557         if (lpMl->dwDestination >= 2)
558             return MMSYSERR_INVALPARAM;
559         if ((ret = MIX_GetLineInfoDst(mix, lpMl, lpMl->dwDestination)) != MMSYSERR_NOERROR)
560             return ret;
561         break;
562     case MIXER_GETLINEINFOF_SOURCE:
563         TRACE("SOURCE (%08lx), dst=%08lx\n", lpMl->dwSource, lpMl->dwDestination);
564         switch (lpMl->dwDestination)
565         {
566         case 0: mask = mix->devMask; break;
567         case 1: mask = mix->recMask; break;
568         default: return MMSYSERR_INVALPARAM;
569         }
570         i = lpMl->dwSource;
571         for (j = 0; j < SOUND_MIXER_NRDEVICES; j++)
572         {
573             if (WINE_CHN_SUPPORTS(mask, j) && (i-- == 0))
574                 break;
575         }
576         if (j >= SOUND_MIXER_NRDEVICES)
577             return MIXERR_INVALLINE;
578         if ((ret = MIX_GetLineInfoSrc(mix, lpMl, j, lpMl->dwDestination)) != MMSYSERR_NOERROR)
579             return ret;
580         break;
581     case MIXER_GETLINEINFOF_LINEID:
582         TRACE("LINEID (%08lx)\n", lpMl->dwLineID);
583
584         if (!MIX_CheckLine(lpMl->dwLineID))
585             return MIXERR_INVALLINE;
586         if (HIWORD(lpMl->dwLineID) == LINEID_DST)
587             ret = MIX_GetLineInfoDst(mix, lpMl, LOWORD(lpMl->dwLineID));
588         else
589             ret = MIX_GetLineInfoSrc(mix, lpMl, HIWORD(lpMl->dwLineID), LOWORD(lpMl->dwLineID));
590         if (ret != MMSYSERR_NOERROR)
591             return ret;
592         break;
593     case MIXER_GETLINEINFOF_COMPONENTTYPE:
594         TRACE("COMPONENT TYPE (%08lx)\n", lpMl->dwComponentType);
595         switch (lpMl->dwComponentType)
596         {
597         case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
598             ret = MIX_GetLineInfoDst(mix, lpMl, 0);
599             break;
600         case MIXERLINE_COMPONENTTYPE_DST_WAVEIN:
601             ret = MIX_GetLineInfoDst(mix, lpMl, 1);
602             break;
603         case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER:
604             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_SYNTH, 0);
605             break;
606         case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC:
607             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_CD, 0);
608             break;
609         case MIXERLINE_COMPONENTTYPE_SRC_LINE:
610             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_LINE, 0);
611             break;
612         case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE:
613             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_MIC, 1);
614             break;
615         case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT:
616             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_PCM, 0);
617             break;
618         case MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED:
619             ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_IMIX, 1);
620             break;
621         default:
622             FIXME("Unhandled component type (%08lx)\n", lpMl->dwComponentType);
623             return MMSYSERR_INVALPARAM;
624         }
625         break;
626     case MIXER_GETLINEINFOF_TARGETTYPE:
627         FIXME("_TARGETTYPE not implemented yet.\n");
628         break;
629     default:
630         WARN("Unknown flag (%08lx)\n", fdwInfo & MIXER_GETLINEINFOF_QUERYMASK);
631         break;
632     }
633
634     lpMl->Target.dwType = MIXERLINE_TARGETTYPE_AUX; /* FIXME */
635     lpMl->Target.dwDeviceID = 0xFFFFFFFF;
636     lpMl->Target.wMid = WINE_MIXER_MANUF_ID;
637     lpMl->Target.wPid = WINE_MIXER_PRODUCT_ID;
638     lpMl->Target.vDriverVersion = WINE_MIXER_VERSION;
639     strcpy(lpMl->Target.szPname, WINE_MIXER_NAME);
640
641     return ret;
642 }
643
644 /******************************************************************
645  *              MIX_CheckControl
646  *
647  */
648 static BOOL     MIX_CheckControl(struct mixer* mix, DWORD ctrlID)
649 {
650     return (ctrlID >= 1 && ctrlID <= mix->numCtrl);
651 }
652
653 /**************************************************************************
654  *                              MIX_GetLineControls             [internal]
655  */
656 static  DWORD   MIX_GetLineControls(WORD wDevID, LPMIXERLINECONTROLSA lpMlc, DWORD flags)
657 {
658     DWORD               dwRet = MMSYSERR_NOERROR;
659     struct mixer*       mix;
660
661     TRACE("(%04X, %p, %lu);\n", wDevID, lpMlc, flags);
662
663     if (lpMlc == NULL) return MMSYSERR_INVALPARAM;
664     if (lpMlc->cbStruct < sizeof(*lpMlc) ||
665         lpMlc->cbmxctrl < sizeof(MIXERCONTROLA))
666         return MMSYSERR_INVALPARAM;
667     if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
668
669     switch (flags & MIXER_GETLINECONTROLSF_QUERYMASK)
670     {
671     case MIXER_GETLINECONTROLSF_ALL:
672         {
673             int         i, j;
674
675             TRACE("line=%08lx GLCF_ALL (%ld)\n", lpMlc->dwLineID, lpMlc->cControls);
676
677             for (i = j = 0; i < mix->numCtrl; i++)
678             {
679                 if (mix->ctrl[i].dwLineID == lpMlc->dwLineID)
680                     j++;
681             }
682             if (!j || lpMlc->cControls != j)            dwRet = MMSYSERR_INVALPARAM;
683             else if (!MIX_CheckLine(lpMlc->dwLineID))   dwRet = MIXERR_INVALLINE;
684             else
685             {
686                 for (i = j = 0; i < mix->numCtrl; i++)
687                 {
688                     if (mix->ctrl[i].dwLineID == lpMlc->dwLineID)
689                     {
690                         TRACE("[%d] => [%2d]: typ=%08lx\n", j, i + 1, mix->ctrl[i].ctrl.dwControlType);
691                         lpMlc->pamxctrl[j++] = mix->ctrl[i].ctrl;
692                     }
693                 }
694             }
695         }
696         break;
697     case MIXER_GETLINECONTROLSF_ONEBYID:
698         TRACE("line=%08lx GLCF_ONEBYID (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlID);
699
700         if (!MIX_CheckControl(mix, lpMlc->u.dwControlID) ||
701             mix->ctrl[lpMlc->u.dwControlID - 1].dwLineID != lpMlc->dwLineID)
702             dwRet = MMSYSERR_INVALPARAM;
703         else
704             lpMlc->pamxctrl[0] = mix->ctrl[lpMlc->u.dwControlID - 1].ctrl;
705         break;
706     case MIXER_GETLINECONTROLSF_ONEBYTYPE:
707         TRACE("line=%08lx GLCF_ONEBYTYPE (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlType);
708         if (!MIX_CheckLine(lpMlc->dwLineID))    dwRet = MIXERR_INVALLINE;
709         else
710         {
711             int         i;
712             DWORD       ct = lpMlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK;
713
714             for (i = 0; i < mix->numCtrl; i++)
715             {
716                 if (mix->ctrl[i].dwLineID == lpMlc->dwLineID &&
717                     ct == (mix->ctrl[i].ctrl.dwControlType & MIXERCONTROL_CT_CLASS_MASK))
718                 {
719                     lpMlc->pamxctrl[0] = mix->ctrl[i].ctrl;
720                     break;
721                 }
722             }
723             if (i == mix->numCtrl) dwRet = MMSYSERR_INVALPARAM;
724         }
725         break;
726     default:
727         ERR("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
728         dwRet = MMSYSERR_INVALPARAM;
729     }
730
731     return dwRet;
732 }
733
734 /**************************************************************************
735  *                              MIX_GetControlDetails           [internal]
736  */
737 static  DWORD   MIX_GetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
738 {
739     DWORD               ret = MMSYSERR_NOTSUPPORTED;
740     DWORD               c, chnl;
741     struct mixer*       mix;
742
743     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
744
745     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
746     if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
747
748     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK)
749     {
750     case MIXER_GETCONTROLDETAILSF_VALUE:
751         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
752         if (MIX_CheckControl(mix, lpmcd->dwControlID))
753         {
754             c = lpmcd->dwControlID - 1;
755             chnl = HIWORD(mix->ctrl[c].dwLineID);
756             if (chnl == LINEID_DST)
757                 chnl = LOWORD(mix->ctrl[c].dwLineID) ? SOUND_MIXER_RECLEV : SOUND_MIXER_VOLUME;
758             switch (mix->ctrl[c].ctrl.dwControlType)
759             {
760             case MIXERCONTROL_CONTROLTYPE_VOLUME:
761                 {
762                     LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
763                     int                                 val;
764
765                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_UNSIGNED), lpmcd->cbDetails);
766                     /* return value is 00RL (4 bytes)... */
767                     if ((val = mix->volume[chnl]) == -1 && !MIX_GetVal(mix, chnl, &val))
768                         return MMSYSERR_INVALPARAM;
769
770                     switch (lpmcd->cChannels)
771                     {
772                     case 1:
773                         /* mono... so R = L */
774                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
775                         mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
776                         break;
777                     case 2:
778                         /* stereo, left is paDetails[0] */
779                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
780                         mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
781                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
782                         mcdu->dwValue = (HIBYTE(LOWORD(val)) * 65536L) / 100;
783                         break;
784                     default:
785                         WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
786                         return MMSYSERR_INVALPARAM;
787                     }
788                     TRACE("=> %08lx\n", mcdu->dwValue);
789                 }
790                 break;
791             case MIXERCONTROL_CONTROLTYPE_MUTE:
792             case MIXERCONTROL_CONTROLTYPE_ONOFF:
793                 {
794                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
795
796                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
797                     /* we mute both channels at the same time */
798                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
799                     mcdb->fValue = (mix->volume[chnl] != -1);
800                     TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
801                 }
802                 break;
803             case MIXERCONTROL_CONTROLTYPE_MIXER:
804             case MIXERCONTROL_CONTROLTYPE_MUX:
805                 {
806                     unsigned                            mask;
807
808                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
809                     if (!MIX_GetRecSrc(mix, &mask))
810                     {
811                         /* FIXME: ENXIO => no mixer installed */
812                         WARN("mixer device not available !\n");
813                         ret = MMSYSERR_ERROR;
814                     }
815                     else
816                     {
817                         LPMIXERCONTROLDETAILS_BOOLEAN   mcdb;
818                         int                             i, j;
819
820                         /* we mute both channels at the same time */
821                         mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
822
823                         for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++)
824                         {
825                             if (WINE_CHN_SUPPORTS(mix->recMask, j))
826                             {
827                                 if (i >= lpmcd->u.cMultipleItems)
828                                     return MMSYSERR_INVALPARAM;
829                                 mcdb[i++].fValue = WINE_CHN_SUPPORTS(mask, j);
830                             }
831                         }
832                     }
833                 }
834                 break;
835             default:
836                 WARN("Unsupported\n");
837             }
838             ret = MMSYSERR_NOERROR;
839         }
840         else
841         {
842             ret = MMSYSERR_INVALPARAM;
843         }
844         break;
845     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
846         TRACE("LIST TEXT (%08lx)\n", lpmcd->dwControlID);
847
848         ret = MMSYSERR_INVALPARAM;
849         if (MIX_CheckControl(mix, lpmcd->dwControlID))
850         {
851             int c = lpmcd->dwControlID - 1;
852
853             if (mix->ctrl[c].ctrl.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX ||
854                 mix->ctrl[c].ctrl.dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER)
855             {
856                 LPMIXERCONTROLDETAILS_LISTTEXTA mcdlt;
857                 int i, j;
858
859                 mcdlt = (LPMIXERCONTROLDETAILS_LISTTEXTA)lpmcd->paDetails;
860                 for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++)
861                 {
862                     if (WINE_CHN_SUPPORTS(mix->recMask, j))
863                     {
864                         mcdlt[i].dwParam1 = MAKELONG(LINEID_RECORD, j);
865                         mcdlt[i].dwParam2 = 0;
866                         strcpy(mcdlt[i].szName, MIX_Names[j]);
867                         i++;
868                     }
869                 }
870                 if (i != lpmcd->u.cMultipleItems) FIXME("bad count\n");
871                 ret = MMSYSERR_NOERROR;
872             }
873         }
874         break;
875     default:
876         WARN("Unknown flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
877     }
878     return ret;
879 }
880
881 /**************************************************************************
882  *                              MIX_SetControlDetails           [internal]
883  */
884 static  DWORD   MIX_SetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
885 {
886     DWORD               ret = MMSYSERR_NOTSUPPORTED;
887     DWORD               c, chnl;
888     int                 val;
889     struct mixer*       mix;
890
891     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
892
893     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
894     if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
895
896     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK)
897     {
898     case MIXER_GETCONTROLDETAILSF_VALUE:
899         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
900         if (MIX_CheckControl(mix, lpmcd->dwControlID))
901         {
902             c = lpmcd->dwControlID - 1;
903             chnl = HIWORD(mix->ctrl[c].dwLineID);
904             if (chnl == LINEID_DST)
905                 chnl = LOWORD(mix->ctrl[c].dwLineID) ? SOUND_MIXER_RECLEV : SOUND_MIXER_VOLUME;
906
907             switch (mix->ctrl[c].ctrl.dwControlType)
908             {
909             case MIXERCONTROL_CONTROLTYPE_VOLUME:
910                 {
911                     LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
912
913                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_UNSIGNED), lpmcd->cbDetails);
914                     /* val should contain 00RL */
915                     switch (lpmcd->cChannels)
916                     {
917                     case 1:
918                         /* mono... so R = L */
919                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
920                         TRACE("Setting RL to %08ld\n", mcdu->dwValue);
921                         val = 0x101 * ((mcdu->dwValue * 100) >> 16);
922                         break;
923                     case 2:
924                         /* stereo, left is paDetails[0] */
925                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
926                         TRACE("Setting L to %08ld\n", mcdu->dwValue);
927                         val = ((mcdu->dwValue * 100) >> 16);
928                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
929                         TRACE("Setting R to %08ld\n", mcdu->dwValue);
930                         val += ((mcdu->dwValue * 100) >> 16) << 8;
931                         break;
932                     default:
933                         WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
934                         return MMSYSERR_INVALPARAM;
935                     }
936
937                     if (mix->volume[chnl] == -1)
938                     {
939                         if (!MIX_SetVal(mix, chnl, val))
940                             return MMSYSERR_INVALPARAM;
941                     }
942                     else
943                     {
944                         mix->volume[chnl] = val;
945                     }
946                 }
947                 ret = MMSYSERR_NOERROR;
948                 break;
949             case MIXERCONTROL_CONTROLTYPE_MUTE:
950             case MIXERCONTROL_CONTROLTYPE_ONOFF:
951                 {
952                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
953
954                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
955                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
956                     if (mcdb->fValue)
957                     {
958                         if (!MIX_GetVal(mix, chnl, &mix->volume[chnl]) || !MIX_SetVal(mix, chnl, 0))
959                             return MMSYSERR_INVALPARAM;
960                     }
961                     else
962                     {
963                         if (mix->volume[chnl] == -1)
964                         {
965                             ret = MMSYSERR_NOERROR;
966                             break;
967                         }
968                         if (!MIX_SetVal(mix, chnl, mix->volume[chnl]))
969                             return MMSYSERR_INVALPARAM;
970                         mix->volume[chnl] = -1;
971                     }
972                 }
973                 ret = MMSYSERR_NOERROR;
974                 break;
975             case MIXERCONTROL_CONTROLTYPE_MIXER:
976             case MIXERCONTROL_CONTROLTYPE_MUX:
977                 {
978                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
979                     unsigned                            mask;
980                     int                                 i, j;
981
982                     TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
983                     /* we mute both channels at the same time */
984                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
985                     mask = 0;
986                     for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++)
987                     {
988                         if (WINE_CHN_SUPPORTS(mix->recMask, j) && mcdb[i++].fValue)
989                         {
990                             /* a mux can only select one line at a time... */
991                             if (mix->singleRecChannel && mask != 0)
992                             {
993                                 FIXME("!!!\n");
994                                 return MMSYSERR_INVALPARAM;
995                             }
996                             mask |= WINE_CHN_MASK(j);
997                         }
998                     }
999                     if (i != lpmcd->u.cMultipleItems) FIXME("bad count\n");
1000                     TRACE("writing %04x as rec src\n", mask);
1001                     if (!MIX_SetRecSrc(mix, mask))
1002                         ERR("Can't write new mixer settings\n");
1003                     else
1004                         ret = MMSYSERR_NOERROR;
1005                 }
1006                 break;
1007             }
1008         }
1009         break;
1010     default:
1011         WARN("Unknown SetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
1012     }
1013     return ret;
1014 }
1015
1016 /**************************************************************************
1017  *                              MIX_Init                        [internal]
1018  */
1019 static  DWORD   MIX_Init(void)
1020 {
1021     int mixer;
1022
1023 #define MIXER_DEV "/dev/mixer"
1024     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0)
1025     {
1026         if (errno == ENODEV || errno == ENXIO)
1027         {
1028             /* no driver present */
1029             return MMSYSERR_NODRIVER;
1030         }
1031         MIX_NumMixers = 0;
1032         return MMSYSERR_ERROR;
1033     }
1034     close(mixer);
1035     MIX_NumMixers = 1;
1036     MIX_Mixers[0].name = MIXER_DEV;
1037     MIX_Open(0, NULL, 0); /* FIXME */
1038 #undef MIXER_DEV
1039     return MMSYSERR_NOERROR;
1040 }
1041
1042 /**************************************************************************
1043  *                              MIX_GetNumDevs                  [internal]
1044  */
1045 static  DWORD   MIX_GetNumDevs(void)
1046 {
1047     return MIX_NumMixers;
1048 }
1049
1050 #endif /* HAVE_OSS */
1051
1052 /**************************************************************************
1053  *                              mxdMessage (WINEOSS.3)
1054  */
1055 DWORD WINAPI OSS_mxdMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
1056                             DWORD dwParam1, DWORD dwParam2)
1057 {
1058 /* TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2); */
1059
1060 #ifdef HAVE_OSS
1061     switch (wMsg)
1062     {
1063     case DRVM_INIT:
1064         return MIX_Init();
1065     case DRVM_EXIT:
1066     case DRVM_ENABLE:
1067     case DRVM_DISABLE:
1068         /* FIXME: Pretend this is supported */
1069         return 0;
1070     case MXDM_GETDEVCAPS:
1071         return MIX_GetDevCaps(wDevID, (LPMIXERCAPSA)dwParam1, dwParam2);
1072     case MXDM_GETLINEINFO:
1073         return MIX_GetLineInfo(wDevID, (LPMIXERLINEA)dwParam1, dwParam2);
1074     case MXDM_GETNUMDEVS:
1075         return MIX_GetNumDevs();
1076     case MXDM_OPEN:
1077         return MMSYSERR_NOERROR;
1078         /* MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2); */
1079     case MXDM_CLOSE:
1080         return MMSYSERR_NOERROR;
1081     case MXDM_GETLINECONTROLS:
1082         return MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSA)dwParam1, dwParam2);
1083     case MXDM_GETCONTROLDETAILS:
1084         return MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
1085     case MXDM_SETCONTROLDETAILS:
1086         return MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
1087     default:
1088         WARN("unknown message %d!\n", wMsg);
1089         return MMSYSERR_NOTSUPPORTED;
1090     }
1091 #else
1092     return MMSYSERR_NOTENABLED;
1093 #endif
1094 }