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