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