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