OleMetaFilePictFromIconAndLabel16: slightly broken, use LPOLESTR16.
[wine] / multimedia / 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 <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <sys/ioctl.h>
15 #include "windef.h"
16 #include "user.h"
17 #include "driver.h"
18 #include "multimedia.h"
19 #include "debugtools.h"
20
21 DEFAULT_DEBUG_CHANNEL(mmaux)
22
23 #ifdef HAVE_OSS
24 #define MIXER_DEV "/dev/mixer"
25
26 #define WINE_MIXER_MANUF_ID             0xAA
27 #define WINE_MIXER_PRODUCT_ID           0x55
28 #define WINE_MIXER_VERSION              0x0100
29 #define WINE_MIXER_NAME                 "WINE OSS Mixer"
30
31 #define WINE_CHN_MASK(_x)               (1L << (_x))
32 #define WINE_CHN_SUPPORTS(_c, _x)       ((_c) & WINE_CHN_MASK(_x))
33 #define WINE_MIXER_MASK                 (WINE_CHN_MASK(SOUND_MIXER_VOLUME) | \
34                                          WINE_CHN_MASK(SOUND_MIXER_BASS)   | \
35                                          WINE_CHN_MASK(SOUND_MIXER_TREBLE) | \
36                                          WINE_CHN_MASK(SOUND_MIXER_SYNTH)  | \
37                                          WINE_CHN_MASK(SOUND_MIXER_PCM)    | \
38                                          WINE_CHN_MASK(SOUND_MIXER_LINE)   | \
39                                          WINE_CHN_MASK(SOUND_MIXER_MIC)    | \
40                                          WINE_CHN_MASK(SOUND_MIXER_CD))
41
42 /**************************************************************************
43  *                              MIX_GetVal                      [internal]
44  */
45 static  BOOL    MIX_GetVal(int chn, int* val)
46 {
47     int         mixer;
48     BOOL        ret = FALSE;
49
50     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
51         /* FIXME: ENXIO => no mixer installed */
52         WARN("mixer device not available !\n");
53     } else {
54         if (ioctl(mixer, MIXER_READ(chn), val) >= 0) {
55             TRACE("Reading %x on %d\n", *val, chn);
56             ret = TRUE;
57         }
58         close(mixer);
59     }
60     return ret;
61 }
62
63 /**************************************************************************
64  *                              MIX_SetVal                      [internal]
65  */
66 static  BOOL    MIX_SetVal(int chn, int* val)
67 {
68     int         mixer;
69     BOOL        ret = FALSE;
70
71     TRACE("Writing %x on %d\n", *val, chn);
72
73     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
74         /* FIXME: ENXIO => no mixer installed */
75         WARN("mixer device not available !\n");
76     } else {
77         if (ioctl(mixer, MIXER_WRITE(chn), val) >= 0) {
78             ret = TRUE;
79         }
80         close(mixer);
81     }
82     return ret;
83 }
84
85 /**************************************************************************
86  *                              MIX_GetDevCaps                  [internal]
87  */
88 static DWORD MIX_GetDevCaps(WORD wDevID, LPMIXERCAPSA lpCaps, DWORD dwSize)
89 {
90     int                 mixer, mask;
91     
92     TRACE("(%04X, %p, %lu);\n", wDevID, lpCaps, dwSize);
93     
94     if (wDevID != 0) return MMSYSERR_BADDEVICEID;
95     if (lpCaps == NULL) return MMSYSERR_INVALPARAM;
96     
97     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
98         /* FIXME: ENXIO => no mixer installed */
99         WARN("mixer device not available !\n");
100         return MMSYSERR_NOTENABLED;
101     }
102     lpCaps->wMid = WINE_MIXER_MANUF_ID;
103     lpCaps->wPid = WINE_MIXER_PRODUCT_ID;
104     lpCaps->vDriverVersion = WINE_MIXER_VERSION;
105     strcpy(lpCaps->szPname, WINE_MIXER_NAME);
106     if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &mask) == -1) {
107         close(mixer);
108         perror("ioctl mixer SOUND_MIXER_DEVMASK");
109         return MMSYSERR_NOTENABLED;
110     }   
111     
112     /* FIXME: can the Linux Mixer differ between multiple mixer targets ? */
113     lpCaps->cDestinations = 1;
114     lpCaps->fdwSupport = 0; /* No bits defined yet */
115     
116     close(mixer);
117     return MMSYSERR_NOERROR;
118 }
119
120 static char *sdlabels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
121 static char *sdnames[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
122
123 /**************************************************************************
124  *                              MIX_GetLineInfoFromIndex        [internal]
125  */
126 static  void    MIX_GetLineInfoFromIndex(LPMIXERLINEA lpMl, int devmask, DWORD idx)
127 {
128     strcpy(lpMl->szShortName, sdlabels[idx]);
129     strcpy(lpMl->szName, sdnames[idx]);
130     lpMl->dwLineID = idx;
131     lpMl->dwDestination = 0; /* index for speakers */
132     lpMl->cConnections = 1;
133     lpMl->cControls = 1;
134     switch (idx) {
135     case SOUND_MIXER_SYNTH:
136         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER;
137         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
138         break;
139     case SOUND_MIXER_CD:
140         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;
141         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
142         break;
143     case SOUND_MIXER_LINE:
144         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_LINE;
145         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
146         break;
147     case SOUND_MIXER_MIC:
148         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
149         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
150         break;
151     case SOUND_MIXER_PCM:
152         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
153         lpMl->fdwLine    |= MIXERLINE_LINEF_SOURCE;
154         break;
155     default:
156         ERR("Index %ld not handled.\n", idx);
157         break;
158     }
159 }
160
161 /**************************************************************************
162  *                              MIX_GetLineInfo                 [internal]
163  */
164 static DWORD MIX_GetLineInfo(WORD wDevID, LPMIXERLINEA lpMl, DWORD fdwInfo)
165 {
166     int                 mixer, i, j;
167     int                 devmask, stereomask;
168     BOOL                isDst = FALSE;
169     DWORD               ret = MMSYSERR_NOERROR;
170     
171     TRACE("(%04X, %p, %lu);\n", wDevID, lpMl, fdwInfo);
172     if (lpMl == NULL || lpMl->cbStruct != sizeof(*lpMl)) 
173         return MMSYSERR_INVALPARAM;
174     if ((mixer = open(MIXER_DEV, O_RDWR)) < 0)
175         return MMSYSERR_NOTENABLED;
176     
177     if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) {
178         close(mixer);
179         perror("ioctl mixer SOUND_MIXER_DEVMASK");
180         return MMSYSERR_NOTENABLED;
181     }
182     devmask &= WINE_MIXER_MASK;
183     if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &stereomask) == -1) {
184         close(mixer);
185         perror("ioctl mixer SOUND_MIXER_STEREODEVS");
186         return MMSYSERR_NOTENABLED;
187     }
188     stereomask &= WINE_MIXER_MASK;
189
190 #if 0
191     int                 recsrc, recmask;
192
193     if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) {
194         close(mixer);
195         perror("ioctl mixer SOUND_MIXER_RECSRC");
196         return MMSYSERR_NOTENABLED;
197     }
198
199     if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) {
200         close(mixer);
201         perror("ioctl mixer SOUND_MIXER_RECMASK");
202         return MMSYSERR_NOTENABLED;
203     }
204 #endif
205
206     /* FIXME: set all the variables correctly... the lines below
207      * are very wrong...
208      */
209     lpMl->fdwLine       = MIXERLINE_LINEF_ACTIVE;
210     lpMl->cChannels     = 1;
211     lpMl->dwUser        = 0;
212     lpMl->cControls     = 1;
213     
214     switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK) {
215     case MIXER_GETLINEINFOF_DESTINATION:
216         TRACE("DESTINATION (%08lx)\n", lpMl->dwDestination);
217         /* FIXME: Linux doesn't seem to support multiple outputs? 
218          * So we have only one output type: Speaker.
219          */
220         lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
221         lpMl->dwSource = 0xFFFFFFFF;
222         lpMl->dwLineID = SOUND_MIXER_VOLUME;
223         strncpy(lpMl->szShortName, sdlabels[SOUND_MIXER_VOLUME], MIXER_SHORT_NAME_CHARS);
224         strncpy(lpMl->szName, sdnames[SOUND_MIXER_VOLUME], MIXER_LONG_NAME_CHARS);
225         
226         /* we have all connections found in the devmask */
227         lpMl->cConnections = 0;
228         for (j = 1; j < SOUND_MIXER_NRDEVICES; j++)
229             if (WINE_CHN_SUPPORTS(devmask, j))
230                 lpMl->cConnections++;
231         if (stereomask & WINE_CHN_MASK(SOUND_MIXER_VOLUME))
232             lpMl->cChannels++;
233         break;
234     case MIXER_GETLINEINFOF_SOURCE:
235         TRACE("SOURCE (%08lx)\n", lpMl->dwSource);
236         i = lpMl->dwSource;
237         for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
238             if (WINE_CHN_SUPPORTS(devmask, j) && (i-- == 0)) 
239                 break;
240         }
241         if (j >= SOUND_MIXER_NRDEVICES)
242             return MIXERR_INVALLINE;
243         if (WINE_CHN_SUPPORTS(stereomask, j))
244             lpMl->cChannels++;
245         MIX_GetLineInfoFromIndex(lpMl, devmask, j);
246         break;
247     case MIXER_GETLINEINFOF_LINEID:
248         TRACE("LINEID (%08lx)\n", lpMl->dwLineID);
249         if (lpMl->dwLineID >= SOUND_MIXER_NRDEVICES)
250             return MIXERR_INVALLINE;
251         if (WINE_CHN_SUPPORTS(stereomask, lpMl->dwLineID))
252             lpMl->cChannels++;
253         MIX_GetLineInfoFromIndex(lpMl, devmask, lpMl->dwLineID);
254         break;
255     case MIXER_GETLINEINFOF_COMPONENTTYPE:
256         TRACE("COMPONENT TYPE (%08lx)\n", lpMl->dwComponentType);
257         
258         switch (lpMl->dwComponentType) {
259         case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
260             i = SOUND_MIXER_VOLUME;
261             lpMl->dwDestination = 0;
262             lpMl->dwSource = 0xFFFFFFFF;
263             isDst = TRUE;
264             break;
265         case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER:
266             i = SOUND_MIXER_SYNTH;
267             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
268             break;
269         case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC:
270             i = SOUND_MIXER_CD;
271             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
272             break;
273         case MIXERLINE_COMPONENTTYPE_SRC_LINE:
274             i = SOUND_MIXER_LINE;
275             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
276             break;
277         case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE:
278             i = SOUND_MIXER_MIC;
279             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
280             break;
281         case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT:
282             i = SOUND_MIXER_PCM;
283             lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
284             break;
285         default:
286             FIXME("Unhandled component type (%08lx)\n", lpMl->dwComponentType);
287             return MMSYSERR_INVALPARAM;
288         }
289         
290         if (WINE_CHN_SUPPORTS(devmask, i)) {
291             strcpy(lpMl->szShortName, sdlabels[i]);
292             strcpy(lpMl->szName, sdnames[i]);
293             lpMl->dwLineID = i;
294         }
295         if (WINE_CHN_SUPPORTS(stereomask, i))
296             lpMl->cChannels++;
297         lpMl->cConnections = 0;
298         if (isDst) {
299             for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
300                 if (WINE_CHN_SUPPORTS(devmask, j)) {
301                     lpMl->cConnections++;
302                 }
303             }
304         }
305         break;
306     case MIXER_GETLINEINFOF_TARGETTYPE:
307         FIXME("_TARGETTYPE not implemented yet.\n");
308         break;
309     default:
310         WARN("Unknown flag (%08lx)\n", fdwInfo & MIXER_GETLINEINFOF_QUERYMASK);
311         break;
312     }
313     
314     lpMl->Target.dwType = MIXERLINE_TARGETTYPE_AUX;
315     lpMl->Target.dwDeviceID = 0xFFFFFFFF;
316     lpMl->Target.wMid = WINE_MIXER_MANUF_ID;
317     lpMl->Target.wPid = WINE_MIXER_PRODUCT_ID;
318     lpMl->Target.vDriverVersion = WINE_MIXER_VERSION;
319     strcpy(lpMl->Target.szPname, WINE_MIXER_NAME);
320     
321     close(mixer);
322     return ret;
323 }
324
325 /**************************************************************************
326  *                              MIX_GetLineInfo                 [internal]
327  */
328 static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD flags)
329 {
330     TRACE("(%04X, %p, %lu);\n", wDevID, lpMod, flags);
331     if (lpMod == NULL) return MMSYSERR_INVALPARAM;
332     /* hmm. We don't keep the mixer device open. So just pretend it works */
333     return MMSYSERR_NOERROR;
334 }
335
336 /**************************************************************************
337  *                              MIX_GetLineControls             [internal]
338  */
339 static  DWORD   MIX_GetLineControls(WORD wDevID, LPMIXERLINECONTROLSA lpMlc, DWORD flags)
340 {
341     LPMIXERCONTROLA     mc;
342
343     TRACE("(%04X, %p, %lu): stub!\n", wDevID, lpMlc, flags);
344     
345     if (lpMlc == NULL) return MMSYSERR_INVALPARAM;
346     if (lpMlc->cbStruct < sizeof(*lpMlc) ||
347         lpMlc->cbmxctrl < sizeof(MIXERCONTROLA))
348         return MMSYSERR_INVALPARAM;
349
350     switch (flags & MIXER_GETLINECONTROLSF_QUERYMASK) {
351     case MIXER_GETLINECONTROLSF_ALL:
352         TRACE("line=%08lx GLCF_ALL (%ld)\n", lpMlc->dwLineID, lpMlc->cControls);
353         if (lpMlc->cControls != 1) 
354             return MMSYSERR_INVALPARAM;
355         break;
356     case MIXER_GETLINECONTROLSF_ONEBYID:
357         TRACE("line=%08lx GLCF_ONEBYID (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlID);
358         if (lpMlc->u.dwControlID != 0) 
359             return MMSYSERR_INVALPARAM;
360         break;
361     case MIXER_GETLINECONTROLSF_ONEBYTYPE:
362         TRACE("line=%08lx GLCF_ONEBYTYPE (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlType);
363         if ((lpMlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK) != MIXERCONTROL_CT_CLASS_FADER)
364             return MMSYSERR_INVALPARAM;
365         break;
366     default:
367         ERR("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
368         return MMSYSERR_INVALPARAM;
369     }
370     TRACE("Returning volume control\n");
371     /* currently, OSS only provides 1 control per line
372      * so one by id == one by type == all
373      */
374     mc = lpMlc->pamxctrl;
375     mc->cbStruct = sizeof(MIXERCONTROLA);
376     /* since we always have a single control per line, we'll use for controlID the lineID */
377     mc->dwControlID = lpMlc->dwLineID;
378     mc->dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
379     mc->fdwControl = 0;
380     mc->cMultipleItems = 0;
381     strncpy(mc->szShortName, "Vol", MIXER_SHORT_NAME_CHARS);
382     strncpy(mc->szName, "Volume", MIXER_LONG_NAME_CHARS);
383     memset(&mc->Bounds, 0, sizeof(mc->Bounds));
384     /* CONTROLTYPE_VOLUME uses the MIXER_CONTROLDETAILS_UNSIGNED struct, 
385      * [0, 100] is the range supported by OSS
386      * FIXME: sounds like MIXERCONTROL_CONTROLTYPE_VOLUME is always between 0 and 65536...
387      * look at conversions done in (Get|Set)ControlDetails to stay in [0, 100] range
388      */
389     mc->Bounds.dw.dwMinimum = 0;
390     mc->Bounds.dw.dwMaximum = 100;
391     memset(&mc->Metrics, 0, sizeof(mc->Metrics));
392     mc->Metrics.cSteps = 0;
393     return MMSYSERR_NOERROR;
394 }
395
396 /**************************************************************************
397  *                              MIX_GetControlDetails           [internal]
398  */
399 static  DWORD   MIX_GetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
400 {
401     DWORD       ret = MMSYSERR_NOTSUPPORTED;
402
403     TRACE("(%04X, %p, %lu)\n", wDevID, lpmcd, fdwDetails);
404     
405     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
406
407     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
408     case MIXER_GETCONTROLDETAILSF_VALUE:
409         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
410         {
411             LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
412             int                                 val;
413
414             /* ControlID == LineID == OSS mixer channel */
415             /* return value is 00RL (4 bytes)... */
416             if (!MIX_GetVal(lpmcd->dwControlID, &val))
417                 return MMSYSERR_INVALPARAM;
418             
419             switch (lpmcd->cChannels) {
420             case 1:
421                 /* mono... so R = L */
422                 mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
423                 mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
424                 break;
425             case 2:
426                 /* stereo, left is paDetails[0] */
427                 mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
428                 mcdu->dwValue = (LOBYTE(LOWORD(val)) * 65536L) / 100;
429                 mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
430                 mcdu->dwValue = (HIBYTE(LOWORD(val)) * 65536L) / 100;
431                 break;
432             default:
433                 WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
434                 return MMSYSERR_INVALPARAM;
435             }
436             TRACE("=> %08lx\n", mcdu->dwValue);
437         }
438         ret = MMSYSERR_NOERROR;
439         break;
440     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
441         FIXME("NIY\n");
442         break;
443     default:
444         WARN("Unknown flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
445     }
446     return ret;
447 }
448
449 /**************************************************************************
450  *                              MIX_SetControlDetails           [internal]
451  */
452 static  DWORD   MIX_SetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
453 {
454     DWORD       ret = MMSYSERR_NOTSUPPORTED;
455     
456     TRACE("(%04X, %p, %lu)\n", wDevID, lpmcd, fdwDetails);
457     
458     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
459     
460     switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
461     case MIXER_GETCONTROLDETAILSF_VALUE:
462         TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
463         {
464             LPMIXERCONTROLDETAILS_UNSIGNED      mcdu;
465             int                                 val;
466
467             /* val should contain 00RL */
468             switch (lpmcd->cChannels) {
469             case 1:
470                 /* mono... so R = L */
471                 mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
472                 TRACE("Setting RL to %08ld\n", mcdu->dwValue);
473                 val = 0x101 * ((mcdu->dwValue * 100) >> 16);
474                 break;
475             case 2:
476                 /* stereo, left is paDetails[0] */
477                 mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 0 * lpmcd->cbDetails);
478                 TRACE("Setting L to %08ld\n", mcdu->dwValue);
479                 val = ((mcdu->dwValue * 100) >> 16);
480                 mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)((char*)lpmcd->paDetails + 1 * lpmcd->cbDetails);
481                 TRACE("Setting R to %08ld\n", mcdu->dwValue);
482                 val += ((mcdu->dwValue * 100) >> 16) << 8;
483                 break;
484             default:
485                 WARN("Unknown cChannels (%ld)\n", lpmcd->cChannels);
486                 return MMSYSERR_INVALPARAM;
487             }
488
489             /* ControlID == LineID == OSS mixer channel */
490             if (!MIX_SetVal(lpmcd->dwControlID, &val))
491                 return MMSYSERR_INVALPARAM;
492         }
493         ret = MMSYSERR_NOERROR;
494         break;
495     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
496         FIXME("NIY\n");
497         break;
498     default:
499         WARN("Unknown GetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
500     }
501     return MMSYSERR_NOTSUPPORTED;
502 }
503
504 #endif /* HAVE_OSS */
505
506 /**************************************************************************
507  *                              mixMessage                      [sample driver]
508  */
509 DWORD WINAPI mixMessage(WORD wDevID, WORD wMsg, DWORD dwUser, 
510                         DWORD dwParam1, DWORD dwParam2)
511 {
512     TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", 
513           wDevID, wMsg, dwUser, dwParam1, dwParam2);
514     
515 #ifdef HAVE_OSS
516     switch(wMsg) {
517     case MXDM_GETDEVCAPS:       
518         return MIX_GetDevCaps(wDevID, (LPMIXERCAPSA)dwParam1, dwParam2);
519     case MXDM_GETLINEINFO:
520         return MIX_GetLineInfo(wDevID, (LPMIXERLINEA)dwParam1, dwParam2);
521     case MXDM_GETNUMDEVS:
522         TRACE("return 1;\n");
523         return 1;
524     case MXDM_OPEN:
525         return MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2);
526     case MXDM_CLOSE:
527         return MMSYSERR_NOERROR;
528     case MXDM_GETLINECONTROLS:
529         return MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSA)dwParam1, dwParam2);
530     case MXDM_GETCONTROLDETAILS:
531         return MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
532     case MXDM_SETCONTROLDETAILS:
533         return MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
534     default:
535         WARN("unknown message %d!\n", wMsg);
536     }
537     return MMSYSERR_NOTSUPPORTED;
538 #else
539     return MMSYSERR_NOTENABLED;
540 #endif
541 }
542
543