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