Require {DECLARE,DEFAULT}_DEBUG_CHANNEL statements to end in a ;
[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 Eric Pouech
8  */
9
10 #include "config.h"
11
12 #include <stdlib.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17 #include <sys/ioctl.h>
18 #include "windef.h"
19 #include "user.h"
20 #include "mmddk.h"
21 #include "oss.h"
22 #include "debugtools.h"
23
24 DEFAULT_DEBUG_CHANNEL(mmaux);
25
26 #ifdef HAVE_OSS
27
28 #define MIXER_DEV "/dev/mixer"
29
30 #define WINE_MIXER_MANUF_ID             0xAA
31 #define WINE_MIXER_PRODUCT_ID           0x55
32 #define WINE_MIXER_VERSION              0x0100
33 #define WINE_MIXER_NAME                 "WINE OSS Mixer"
34
35 #define WINE_CHN_MASK(_x)               (1L << (_x))
36 #define WINE_CHN_SUPPORTS(_c, _x)       ((_c) & WINE_CHN_MASK(_x))
37 /* Bass and Treble are no longer in the mask as Windows does not handle them */
38 #define WINE_MIXER_MASK                 (WINE_CHN_MASK(SOUND_MIXER_VOLUME) | \
39                                          WINE_CHN_MASK(SOUND_MIXER_SYNTH)  | \
40                                          WINE_CHN_MASK(SOUND_MIXER_PCM)    | \
41                                          WINE_CHN_MASK(SOUND_MIXER_LINE)   | \
42                                          WINE_CHN_MASK(SOUND_MIXER_MIC)    | \
43                                          WINE_CHN_MASK(SOUND_MIXER_CD))
44
45 /* FIXME: the two following string arrays should be moved to a resource file in a string table */
46 /* if it's done, better use a struct to hold labels, name, and muted channel volume cache */
47 static char*    MIX_Labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
48 static char*    MIX_Names [SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
49 static int      MIX_Volume[SOUND_MIXER_NRDEVICES];
50 static int      MIX_DevMask = 0;
51 static int      MIX_StereoMask = 0;
52
53 /**************************************************************************
54  *                              MIX_GetVal                      [internal]
55  */
56 static  BOOL    MIX_GetVal(int chn, int* val)
57 {
58     int         mixer;
59     BOOL        ret = FALSE;
60
61     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
62         /* FIXME: ENXIO => no mixer installed */
63         WARN("mixer device not available !\n");
64     } else {
65         if (ioctl(mixer, MIXER_READ(chn), val) >= 0) {
66             TRACE("Reading %x on %d\n", *val, chn);
67             ret = TRUE;
68         }
69         close(mixer);
70     }
71     return ret;
72 }
73
74 /**************************************************************************
75  *                              MIX_SetVal                      [internal]
76  */
77 static  BOOL    MIX_SetVal(int chn, int val)
78 {
79     int         mixer;
80     BOOL        ret = FALSE;
81
82     TRACE("Writing %x on %d\n", val, chn);
83
84     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
85         /* FIXME: ENXIO => no mixer installed */
86         WARN("mixer device not available !\n");
87     } else {
88         if (ioctl(mixer, MIXER_WRITE(chn), &val) >= 0) {
89             ret = TRUE;
90         }
91         close(mixer);
92     }
93     return ret;
94 }
95
96 /**************************************************************************
97  *                              MIX_GetDevCaps                  [internal]
98  */
99 static DWORD MIX_GetDevCaps(WORD wDevID, LPMIXERCAPSA lpCaps, DWORD dwSize)
100 {
101     TRACE("(%04X, %p, %lu);\n", wDevID, lpCaps, dwSize);
102     
103     if (wDevID != 0) return MMSYSERR_BADDEVICEID;
104     if (lpCaps == NULL) return MMSYSERR_INVALPARAM;
105     if (!MIX_DevMask) return MMSYSERR_NOTENABLED;
106
107     lpCaps->wMid = WINE_MIXER_MANUF_ID;
108     lpCaps->wPid = WINE_MIXER_PRODUCT_ID;
109     lpCaps->vDriverVersion = WINE_MIXER_VERSION;
110     strcpy(lpCaps->szPname, WINE_MIXER_NAME);
111
112     lpCaps->cDestinations = 1;
113     lpCaps->fdwSupport = 0; /* No bits defined yet */
114     
115     return MMSYSERR_NOERROR;
116 }
117
118 /**************************************************************************
119  *                              MIX_GetLineInfoFromIndex        [internal]
120  */
121 static  DWORD   MIX_GetLineInfoFromIndex(LPMIXERLINEA lpMl, int devmask, DWORD idx)
122 {
123     strcpy(lpMl->szShortName, MIX_Labels[idx]);
124     strcpy(lpMl->szName, MIX_Names[idx]);
125     lpMl->dwLineID = idx;
126     lpMl->dwDestination = 0; /* index for speakers */
127     lpMl->cConnections = 1;
128     lpMl->cControls = 2;
129     switch (idx) {
130     case SOUND_MIXER_SYNTH:
131         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER;
132         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
133         break;
134     case SOUND_MIXER_CD:
135         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;
136         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
137         break;
138     case SOUND_MIXER_LINE:
139         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_LINE;
140         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
141         break;
142     case SOUND_MIXER_MIC:
143         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
144         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
145         break;
146     case SOUND_MIXER_PCM:
147         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
148         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
149         break;
150     default:
151         WARN("Index %ld not handled.\n", idx);
152         return MIXERR_INVALLINE;
153     }
154     return MMSYSERR_NOERROR;
155 }
156
157 /**************************************************************************
158  *                              MIX_GetLineInfo                 [internal]
159  */
160 static DWORD MIX_GetLineInfo(WORD wDevID, LPMIXERLINEA lpMl, DWORD fdwInfo)
161 {
162     int                 i, j;
163     BOOL                isDst = FALSE;
164     DWORD               ret = MMSYSERR_NOERROR;
165     
166     TRACE("(%04X, %p, %lu);\n", wDevID, lpMl, fdwInfo);
167     if (lpMl == NULL || lpMl->cbStruct != sizeof(*lpMl)) 
168         return MMSYSERR_INVALPARAM;
169     
170     /* FIXME: set all the variables correctly... the lines below
171      * are very wrong...
172      */
173     lpMl->fdwLine       = MIXERLINE_LINEF_ACTIVE;
174     lpMl->cChannels     = 1;
175     lpMl->dwUser        = 0;
176     lpMl->cControls     = 2;
177     
178     switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK) {
179     case MIXER_GETLINEINFOF_DESTINATION:
180         TRACE("DESTINATION (%08lx)\n", lpMl->dwDestination);
181         /* FIXME: Linux doesn't seem to support multiple outputs? 
182          * So we have only one output type: Speaker.
183          */
184         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
185         lpMl->dwSource = 0xFFFFFFFF;
186         lpMl->dwLineID = SOUND_MIXER_VOLUME;
187         lstrcpynA(lpMl->szShortName, MIX_Labels[SOUND_MIXER_VOLUME], MIXER_SHORT_NAME_CHARS);
188         lstrcpynA(lpMl->szName, MIX_Names[SOUND_MIXER_VOLUME], MIXER_LONG_NAME_CHARS);
189         
190         /* we have all connections found in the MIX_DevMask */
191         lpMl->cConnections = 0;
192         for (j = 1; j < SOUND_MIXER_NRDEVICES; j++)
193             if (WINE_CHN_SUPPORTS(MIX_DevMask, j))
194                 lpMl->cConnections++;
195         if (WINE_CHN_SUPPORTS(MIX_StereoMask, SOUND_MIXER_VOLUME))
196             lpMl->cChannels++;
197         break;
198     case MIXER_GETLINEINFOF_SOURCE:
199         TRACE("SOURCE (%08lx)\n", lpMl->dwSource);
200         i = lpMl->dwSource;
201         for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
202             if (WINE_CHN_SUPPORTS(MIX_DevMask, j) && (i-- == 0)) 
203                 break;
204         }
205         if (j >= SOUND_MIXER_NRDEVICES)
206             return MIXERR_INVALLINE;
207         if (WINE_CHN_SUPPORTS(MIX_StereoMask, j))
208             lpMl->cChannels++;
209         if ((ret = MIX_GetLineInfoFromIndex(lpMl, MIX_DevMask, j)) != MMSYSERR_NOERROR)
210             return ret;
211         break;
212     case MIXER_GETLINEINFOF_LINEID:
213         TRACE("LINEID (%08lx)\n", lpMl->dwLineID);
214         if (lpMl->dwLineID >= SOUND_MIXER_NRDEVICES)
215             return MIXERR_INVALLINE;
216         if (WINE_CHN_SUPPORTS(MIX_StereoMask, lpMl->dwLineID))
217             lpMl->cChannels++;
218         if ((ret = MIX_GetLineInfoFromIndex(lpMl, MIX_DevMask, lpMl->dwLineID)) != MMSYSERR_NOERROR)
219             return ret;
220         break;
221     case MIXER_GETLINEINFOF_COMPONENTTYPE:
222         TRACE("COMPONENT TYPE (%08lx)\n", lpMl->dwComponentType);
223         
224         switch (lpMl->dwComponentType) {
225         case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
226             i = SOUND_MIXER_VOLUME;
227             lpMl->dwDestination = 0;
228             lpMl->dwSource = 0xFFFFFFFF;
229             isDst = TRUE;
230             break;
231         case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER:
232             i = SOUND_MIXER_SYNTH;
233             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
234             break;
235         case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC:
236             i = SOUND_MIXER_CD;
237             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
238             break;
239         case MIXERLINE_COMPONENTTYPE_SRC_LINE:
240             i = SOUND_MIXER_LINE;
241             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
242             break;
243         case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE:
244             i = SOUND_MIXER_MIC;
245             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
246             break;
247         case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT:
248             i = SOUND_MIXER_PCM;
249             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
250             break;
251         default:
252             FIXME("Unhandled component type (%08lx)\n", lpMl->dwComponentType);
253             return MMSYSERR_INVALPARAM;
254         }
255         
256         if (WINE_CHN_SUPPORTS(MIX_DevMask, i)) {
257             strcpy(lpMl->szShortName, MIX_Labels[i]);
258             strcpy(lpMl->szName, MIX_Names[i]);
259             lpMl->dwLineID = i;
260         }
261         if (WINE_CHN_SUPPORTS(MIX_StereoMask, i))
262             lpMl->cChannels++;
263         lpMl->cConnections = 0;
264         if (isDst) {
265             for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
266                 if (WINE_CHN_SUPPORTS(MIX_DevMask, j)) {
267                     lpMl->cConnections++;
268                 }
269             }
270         }
271         break;
272     case MIXER_GETLINEINFOF_TARGETTYPE:
273         FIXME("_TARGETTYPE not implemented yet.\n");
274         break;
275     default:
276         WARN("Unknown flag (%08lx)\n", fdwInfo & MIXER_GETLINEINFOF_QUERYMASK);
277         break;
278     }
279     
280     lpMl->Target.dwType = MIXERLINE_TARGETTYPE_AUX;
281     lpMl->Target.dwDeviceID = 0xFFFFFFFF;
282     lpMl->Target.wMid = WINE_MIXER_MANUF_ID;
283     lpMl->Target.wPid = WINE_MIXER_PRODUCT_ID;
284     lpMl->Target.vDriverVersion = WINE_MIXER_VERSION;
285     strcpy(lpMl->Target.szPname, WINE_MIXER_NAME);
286     
287     return ret;
288 }
289
290 /**************************************************************************
291  *                              MIX_GetLineInfo                 [internal]
292  */
293 static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD flags)
294 {
295     TRACE("(%04X, %p, %lu);\n", wDevID, lpMod, flags);
296     if (lpMod == NULL) return MMSYSERR_INVALPARAM;
297     
298     return (MIX_DevMask == 0) ? MMSYSERR_NODRIVER : MMSYSERR_NOERROR;
299 }
300
301 /**************************************************************************
302  *                              MIX_MakeControlID               [internal]
303  */
304 static DWORD MIX_MakeControlID(DWORD lineID, DWORD controlType)
305 {
306     switch (controlType) {
307     case MIXERCONTROL_CONTROLTYPE_VOLUME:
308         return 2 * lineID + 0;
309     case MIXERCONTROL_CONTROLTYPE_MUTE:
310         return 2 * lineID + 1;
311     }
312     FIXME("Internal error");
313     return 0x00FADE00;
314 }
315
316 /**************************************************************************
317  *                              MIX_SplitControlID              [internal]
318  */
319 static BOOL MIX_SplitControlID(DWORD controlID, LPDWORD lineID, LPDWORD controlType)
320 {
321     *lineID = controlID / 2;
322     *controlType = (controlID & 1) ? 
323         MIXERCONTROL_CONTROLTYPE_MUTE : MIXERCONTROL_CONTROLTYPE_VOLUME;
324
325     return *lineID < SOUND_MIXER_NRDEVICES && WINE_CHN_SUPPORTS(MIX_DevMask, *lineID);
326 }
327
328 /**************************************************************************
329  *                              MIX_DoGetLineControls           [internal]
330  */
331 static void MIX_DoGetLineControls(LPMIXERCONTROLA mc, DWORD lineID, DWORD dwType)
332 {
333     mc->cbStruct = sizeof(MIXERCONTROLA);
334     
335     switch (dwType) {
336     case MIXERCONTROL_CONTROLTYPE_VOLUME:
337         TRACE("Returning volume control\n");
338         mc->dwControlID = MIX_MakeControlID(lineID, dwType);
339         mc->dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
340         mc->fdwControl = 0;
341         mc->cMultipleItems = 0;
342         lstrcpynA(mc->szShortName, "Vol", MIXER_SHORT_NAME_CHARS);
343         lstrcpynA(mc->szName, "Volume", MIXER_LONG_NAME_CHARS);
344         memset(&mc->Bounds, 0, sizeof(mc->Bounds));
345         /* CONTROLTYPE_VOLUME uses the MIXER_CONTROLDETAILS_UNSIGNED struct, 
346          * [0, 100] is the range supported by OSS
347          * whatever the min and max values are they must match
348          * conversions done in (Get|Set)ControlDetails to stay in [0, 100] range
349          */
350         mc->Bounds.s1.dwMinimum = 0;
351         mc->Bounds.s1.dwMaximum = 65535;
352         memset(&mc->Metrics, 0, sizeof(mc->Metrics));
353         break;
354     case MIXERCONTROL_CONTROLTYPE_MUTE:
355         TRACE("Returning mute control\n");
356         mc->dwControlID = MIX_MakeControlID(lineID, dwType);
357         mc->dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
358         mc->fdwControl = 0;
359         mc->cMultipleItems = 0;
360         lstrcpynA(mc->szShortName, "Mute", MIXER_SHORT_NAME_CHARS);
361         lstrcpynA(mc->szName, "Mute", MIXER_LONG_NAME_CHARS);
362         memset(&mc->Bounds, 0, sizeof(mc->Bounds));
363         memset(&mc->Metrics, 0, sizeof(mc->Metrics));
364         break;
365     default:
366         FIXME("Internal error: unknown type: %08lx\n", dwType);
367     }
368 }
369
370 /**************************************************************************
371  *                              MIX_GetLineControls             [internal]
372  */
373 static  DWORD   MIX_GetLineControls(WORD wDevID, LPMIXERLINECONTROLSA lpMlc, DWORD flags)
374 {
375     DWORD               dwRet = MMSYSERR_NOERROR;
376     DWORD               lineID, controlType;
377
378     TRACE("(%04X, %p, %lu);\n", wDevID, lpMlc, flags);
379     
380     if (lpMlc == NULL) return MMSYSERR_INVALPARAM;
381     if (lpMlc->cbStruct < sizeof(*lpMlc) ||
382         lpMlc->cbmxctrl < sizeof(MIXERCONTROLA))
383         return MMSYSERR_INVALPARAM;
384
385     switch (flags & MIXER_GETLINECONTROLSF_QUERYMASK) {
386     case MIXER_GETLINECONTROLSF_ALL:
387         TRACE("line=%08lx GLCF_ALL (%ld)\n", lpMlc->dwLineID, lpMlc->cControls);
388         if (lpMlc->cControls != 2) {
389             dwRet = MMSYSERR_INVALPARAM;
390         } else {
391             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME);
392             MIX_DoGetLineControls(&lpMlc->pamxctrl[1], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_MUTE);
393         }
394         break;
395     case MIXER_GETLINECONTROLSF_ONEBYID:
396         TRACE("line=%08lx GLCF_ONEBYID (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlID);
397         if (MIX_SplitControlID(lpMlc->u.dwControlID, &lineID, &controlType))
398             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lineID, controlType);
399         else 
400             dwRet = MMSYSERR_INVALPARAM;
401         break;
402     case MIXER_GETLINECONTROLSF_ONEBYTYPE:
403         TRACE("line=%08lx GLCF_ONEBYTYPE (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlType);
404         switch (lpMlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK) {
405         case MIXERCONTROL_CT_CLASS_FADER:
406             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME);
407             break;
408         case MIXERCONTROL_CT_CLASS_SWITCH:
409             MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_MUTE);
410             break;
411         default:
412             dwRet = MMSYSERR_INVALPARAM;
413         }
414         break;
415     default:
416         ERR("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
417         dwRet = MMSYSERR_INVALPARAM;
418     }
419
420     return dwRet;
421 }
422
423 /**************************************************************************
424  *                              MIX_GetControlDetails           [internal]
425  */
426 static  DWORD   MIX_GetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
427 {
428     DWORD       ret = MMSYSERR_NOTSUPPORTED;
429     DWORD       lineID, controlType;
430
431     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
432     
433     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
434
435     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
436     case MIXER_GETCONTROLDETAILSF_VALUE:
437         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
438         if (MIX_SplitControlID(lpmcd->dwControlID, &lineID, &controlType)) {
439             switch (controlType) {
440             case MIXERCONTROL_CONTROLTYPE_VOLUME:
441                 {
442                     LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
443                     int                                 val;
444
445                     /* return value is 00RL (4 bytes)... */
446                     if ((val = MIX_Volume[lineID]) == -1 && !MIX_GetVal(lineID, &val))
447                         return MMSYSERR_INVALPARAM;
448             
449                     switch (lpmcd->cChannels) {
450                     case 1:
451                         /* mono... so R = L */
452                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
453                         mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
454                         break;
455                     case 2:
456                         /* stereo, left is paDetails[0] */
457                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
458                         mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
459                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
460                         mcdu->dwValue = (HIBYTE(LOWORD(val)) * 65536L) / 100;
461                         break;
462                     default:
463                         WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
464                         return MMSYSERR_INVALPARAM;
465                     }
466                     TRACE("=> %08lx\n", mcdu->dwValue);
467                 }
468                 break;
469             case MIXERCONTROL_CONTROLTYPE_MUTE:
470                 {
471                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
472                     
473                     /* we mute both channels at the same time */
474                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
475                     mcdb->fValue = (MIX_Volume[lineID] != -1);
476                 }
477                 break;
478             }
479             ret = MMSYSERR_NOERROR;
480         } else {
481             ret = MMSYSERR_INVALPARAM;
482         }
483         break;
484     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
485         FIXME("NIY\n");
486         break;
487     default:
488         WARN("Unknown flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
489     }
490     return ret;
491 }
492
493 /**************************************************************************
494  *                              MIX_SetControlDetails           [internal]
495  */
496 static  DWORD   MIX_SetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
497 {
498     DWORD       ret = MMSYSERR_NOTSUPPORTED;
499     DWORD       lineID, controlType;
500     int         val;
501     
502     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
503     
504     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
505     
506     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
507     case MIXER_GETCONTROLDETAILSF_VALUE:
508         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
509         if (MIX_SplitControlID(lpmcd->dwControlID, &lineID, &controlType)) {
510             switch (controlType) {
511             case MIXERCONTROL_CONTROLTYPE_VOLUME:
512                 {
513                     LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
514                     
515                     /* val should contain 00RL */
516                     switch (lpmcd->cChannels) {
517                     case 1:
518                         /* mono... so R = L */
519                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
520                         TRACE("Setting RL to %08ld\n", mcdu->dwValue);
521                         val = 0x101 * ((mcdu->dwValue * 100) >> 16);
522                         break;
523                     case 2:
524                         /* stereo, left is paDetails[0] */
525                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
526                         TRACE("Setting L to %08ld\n", mcdu->dwValue);
527                         val = ((mcdu->dwValue * 100) >> 16);
528                         mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
529                         TRACE("Setting R to %08ld\n", mcdu->dwValue);
530                         val += ((mcdu->dwValue * 100) >> 16) << 8;
531                         break;
532                     default:
533                         WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
534                         return MMSYSERR_INVALPARAM;
535                     }
536                     
537                     if (MIX_Volume[lineID] == -1) {
538                         if (!MIX_SetVal(lineID, val))
539                             return MMSYSERR_INVALPARAM;
540                     } else {
541                         MIX_Volume[lineID] = val;
542                     }
543                 }
544                 ret = MMSYSERR_NOERROR;
545                 break;
546             case MIXERCONTROL_CONTROLTYPE_MUTE:
547                 {
548                     LPMIXERCONTROLDETAILS_BOOLEAN       mcdb;
549                     
550                     mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
551                     if (mcdb->fValue) {
552                         if (!MIX_GetVal(lineID, &MIX_Volume[lineID]))
553                             return MMSYSERR_INVALPARAM;
554                         if (!MIX_SetVal(lineID, 0))
555                             return MMSYSERR_INVALPARAM;
556                     } else {
557                         if (MIX_Volume[lineID] == -1) {
558                                 ret = MMSYSERR_NOERROR;
559                                 break;
560                         }
561                         if (!MIX_SetVal(lineID, MIX_Volume[lineID]))
562                             return MMSYSERR_INVALPARAM;
563                         MIX_Volume[lineID] = -1;
564                     }
565                 }
566                 ret = MMSYSERR_NOERROR;
567                 break;
568             }
569         }
570         break;
571     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
572         FIXME("NIY\n");
573         break;
574     default:
575         WARN("Unknown GetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
576     }
577     return MMSYSERR_NOTSUPPORTED;
578 }
579
580 /**************************************************************************
581  *                              MIX_GetNumDevs                  [internal]
582  */
583 static  DWORD   MIX_GetNumDevs(UINT wDevID)
584 {
585     return (MIX_DevMask != 0) ? 1 : 0;
586 }
587
588 /**************************************************************************
589  *                              MIX_Init                        [internal]
590  */
591 static  DWORD   MIX_Init(void)
592 {
593     int mixer, i;
594
595     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
596         if (errno == ENODEV || errno == ENXIO) {        
597             /* no driver present */
598             return MMSYSERR_NODRIVER;
599         }
600         return MMSYSERR_ERROR;
601     }
602     if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &MIX_DevMask) == -1) {
603         close(mixer);
604         perror("ioctl mixer SOUND_MIXER_DEVMASK");
605         return MMSYSERR_NOTENABLED;
606     }
607     MIX_DevMask &= WINE_MIXER_MASK;
608     if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &MIX_StereoMask) == -1) {
609         close(mixer);
610         perror("ioctl mixer SOUND_MIXER_STEREODEVS");
611         return MMSYSERR_NOTENABLED;
612     }
613     MIX_StereoMask &= WINE_MIXER_MASK;
614
615 #if 0
616     int                 recsrc, recmask;
617
618     if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) {
619         close(mixer);
620         perror("ioctl mixer SOUND_MIXER_RECSRC");
621         return MMSYSERR_NOTENABLED;
622     }
623
624     if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) {
625         close(mixer);
626         perror("ioctl mixer SOUND_MIXER_RECMASK");
627         return MMSYSERR_NOTENABLED;
628     }
629 #endif
630     for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
631         MIX_Volume[i] = -1;
632     }
633     close(mixer);
634
635     return MMSYSERR_NOERROR;
636 }
637
638 #endif /* HAVE_OSS */
639
640 /**************************************************************************
641  *                              OSS_mixMessage          [sample driver]
642  */
643 DWORD WINAPI OSS_mixMessage(UINT wDevID, UINT wMsg, DWORD dwUser, 
644                             DWORD dwParam1, DWORD dwParam2)
645 {
646     TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", 
647           wDevID, wMsg, dwUser, dwParam1, dwParam2);
648     
649 #ifdef HAVE_OSS
650     switch(wMsg) {
651     case DRVM_INIT:
652         return MIX_Init();
653         break; 
654     case DRVM_EXIT:
655     case DRVM_ENABLE:
656     case DRVM_DISABLE:
657         /* FIXME: Pretend this is supported */
658         return 0;
659     case MXDM_GETDEVCAPS:       
660         return MIX_GetDevCaps(wDevID, (LPMIXERCAPSA)dwParam1, dwParam2);
661     case MXDM_GETLINEINFO:
662         return MIX_GetLineInfo(wDevID, (LPMIXERLINEA)dwParam1, dwParam2);
663     case MXDM_GETNUMDEVS:
664         return MIX_GetNumDevs(wDevID);
665     case MXDM_OPEN:
666         return MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2);
667     case MXDM_CLOSE:
668         return MMSYSERR_NOERROR;
669     case MXDM_GETLINECONTROLS:
670         return MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSA)dwParam1, dwParam2);
671     case MXDM_GETCONTROLDETAILS:
672         return MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
673     case MXDM_SETCONTROLDETAILS:
674         return MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
675     default:
676         WARN("unknown message %d!\n", wMsg);
677     }
678     return MMSYSERR_NOTSUPPORTED;
679 #else
680     return MMSYSERR_NOTENABLED;
681 #endif
682 }
683
684