1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
4 * Sample MIXER Wine Driver for Linux
6 * Copyright 1997 Marcus Meissner
14 #include <sys/ioctl.h>
18 #include "multimedia.h"
19 #include "debugtools.h"
21 DEFAULT_DEBUG_CHANNEL(mmaux
)
24 #define MIXER_DEV "/dev/mixer"
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"
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))
42 /**************************************************************************
43 * MIX_GetVal [internal]
45 static BOOL
MIX_GetVal(int chn
, int* val
)
50 if ((mixer
= open(MIXER_DEV
, O_RDWR
)) < 0) {
51 /* FIXME: ENXIO => no mixer installed */
52 WARN("mixer device not available !\n");
54 if (ioctl(mixer
, MIXER_READ(chn
), val
) >= 0) {
55 TRACE("Reading %x on %d\n", *val
, chn
);
63 /**************************************************************************
64 * MIX_SetVal [internal]
66 static BOOL
MIX_SetVal(int chn
, int* val
)
71 TRACE("Writing %x on %d\n", *val
, chn
);
73 if ((mixer
= open(MIXER_DEV
, O_RDWR
)) < 0) {
74 /* FIXME: ENXIO => no mixer installed */
75 WARN("mixer device not available !\n");
77 if (ioctl(mixer
, MIXER_WRITE(chn
), val
) >= 0) {
85 /**************************************************************************
86 * MIX_GetDevCaps [internal]
88 static DWORD
MIX_GetDevCaps(WORD wDevID
, LPMIXERCAPSA lpCaps
, DWORD dwSize
)
92 TRACE("(%04X, %p, %lu);\n", wDevID
, lpCaps
, dwSize
);
94 if (wDevID
!= 0) return MMSYSERR_BADDEVICEID
;
95 if (lpCaps
== NULL
) return MMSYSERR_INVALPARAM
;
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
;
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) {
108 perror("ioctl mixer SOUND_MIXER_DEVMASK");
109 return MMSYSERR_NOTENABLED
;
112 /* FIXME: can the Linux Mixer differ between multiple mixer targets ? */
113 lpCaps
->cDestinations
= 1;
114 lpCaps
->fdwSupport
= 0; /* No bits defined yet */
117 return MMSYSERR_NOERROR
;
120 static char *sdlabels
[SOUND_MIXER_NRDEVICES
] = SOUND_DEVICE_LABELS
;
121 static char *sdnames
[SOUND_MIXER_NRDEVICES
] = SOUND_DEVICE_NAMES
;
123 /**************************************************************************
124 * MIX_GetLineInfoFromIndex [internal]
126 static void MIX_GetLineInfoFromIndex(LPMIXERLINEA lpMl
, int devmask
, DWORD idx
)
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;
135 case SOUND_MIXER_SYNTH
:
136 lpMl
->dwComponentType
= MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER
;
137 lpMl
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
140 lpMl
->dwComponentType
= MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC
;
141 lpMl
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
143 case SOUND_MIXER_LINE
:
144 lpMl
->dwComponentType
= MIXERLINE_COMPONENTTYPE_SRC_LINE
;
145 lpMl
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
147 case SOUND_MIXER_MIC
:
148 lpMl
->dwComponentType
= MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
;
149 lpMl
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
151 case SOUND_MIXER_PCM
:
152 lpMl
->dwComponentType
= MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT
;
153 lpMl
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
156 ERR("Index %ld not handled.\n", idx
);
161 /**************************************************************************
162 * MIX_GetLineInfo [internal]
164 static DWORD
MIX_GetLineInfo(WORD wDevID
, LPMIXERLINEA lpMl
, DWORD fdwInfo
)
167 int devmask
, stereomask
;
169 DWORD ret
= MMSYSERR_NOERROR
;
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
;
177 if (ioctl(mixer
, SOUND_MIXER_READ_DEVMASK
, &devmask
) == -1) {
179 perror("ioctl mixer SOUND_MIXER_DEVMASK");
180 return MMSYSERR_NOTENABLED
;
182 devmask
&= WINE_MIXER_MASK
;
183 if (ioctl(mixer
, SOUND_MIXER_READ_STEREODEVS
, &stereomask
) == -1) {
185 perror("ioctl mixer SOUND_MIXER_STEREODEVS");
186 return MMSYSERR_NOTENABLED
;
188 stereomask
&= WINE_MIXER_MASK
;
193 if (ioctl(mixer
, SOUND_MIXER_READ_RECSRC
, &recsrc
) == -1) {
195 perror("ioctl mixer SOUND_MIXER_RECSRC");
196 return MMSYSERR_NOTENABLED
;
199 if (ioctl(mixer
, SOUND_MIXER_READ_RECMASK
, &recmask
) == -1) {
201 perror("ioctl mixer SOUND_MIXER_RECMASK");
202 return MMSYSERR_NOTENABLED
;
206 /* FIXME: set all the variables correctly... the lines below
209 lpMl
->fdwLine
= MIXERLINE_LINEF_ACTIVE
;
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.
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
);
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
))
234 case MIXER_GETLINEINFOF_SOURCE
:
235 TRACE("SOURCE (%08lx)\n", lpMl
->dwSource
);
237 for (j
= 1; j
< SOUND_MIXER_NRDEVICES
; j
++) {
238 if (WINE_CHN_SUPPORTS(devmask
, j
) && (i
-- == 0))
241 if (j
>= SOUND_MIXER_NRDEVICES
)
242 return MIXERR_INVALLINE
;
243 if (WINE_CHN_SUPPORTS(stereomask
, j
))
245 MIX_GetLineInfoFromIndex(lpMl
, devmask
, j
);
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
))
253 MIX_GetLineInfoFromIndex(lpMl
, devmask
, lpMl
->dwLineID
);
255 case MIXER_GETLINEINFOF_COMPONENTTYPE
:
256 TRACE("COMPONENT TYPE (%08lx)\n", lpMl
->dwComponentType
);
258 switch (lpMl
->dwComponentType
) {
259 case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS
:
260 i
= SOUND_MIXER_VOLUME
;
261 lpMl
->dwDestination
= 0;
262 lpMl
->dwSource
= 0xFFFFFFFF;
265 case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER
:
266 i
= SOUND_MIXER_SYNTH
;
267 lpMl
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
269 case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC
:
271 lpMl
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
273 case MIXERLINE_COMPONENTTYPE_SRC_LINE
:
274 i
= SOUND_MIXER_LINE
;
275 lpMl
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
277 case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE
:
279 lpMl
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
281 case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT
:
283 lpMl
->fdwLine
|= MIXERLINE_LINEF_SOURCE
;
286 FIXME("Unhandled component type (%08lx)\n", lpMl
->dwComponentType
);
287 return MMSYSERR_INVALPARAM
;
290 if (WINE_CHN_SUPPORTS(devmask
, i
)) {
291 strcpy(lpMl
->szShortName
, sdlabels
[i
]);
292 strcpy(lpMl
->szName
, sdnames
[i
]);
295 if (WINE_CHN_SUPPORTS(stereomask
, i
))
297 lpMl
->cConnections
= 0;
299 for (j
= 1; j
< SOUND_MIXER_NRDEVICES
; j
++) {
300 if (WINE_CHN_SUPPORTS(devmask
, j
)) {
301 lpMl
->cConnections
++;
306 case MIXER_GETLINEINFOF_TARGETTYPE
:
307 FIXME("_TARGETTYPE not implemented yet.\n");
310 WARN("Unknown flag (%08lx)\n", fdwInfo
& MIXER_GETLINEINFOF_QUERYMASK
);
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
);
325 /**************************************************************************
326 * MIX_GetLineInfo [internal]
328 static DWORD
MIX_Open(WORD wDevID
, LPMIXEROPENDESC lpMod
, DWORD flags
)
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
;
336 /**************************************************************************
337 * MIX_GetLineControls [internal]
339 static DWORD
MIX_GetLineControls(WORD wDevID
, LPMIXERLINECONTROLSA lpMlc
, DWORD flags
)
343 TRACE("(%04X, %p, %lu): stub!\n", wDevID
, lpMlc
, flags
);
345 if (lpMlc
== NULL
) return MMSYSERR_INVALPARAM
;
346 if (lpMlc
->cbStruct
< sizeof(*lpMlc
) ||
347 lpMlc
->cbmxctrl
< sizeof(MIXERCONTROLA
))
348 return MMSYSERR_INVALPARAM
;
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
;
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
;
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
;
367 ERR("Unknown flag %08lx\n", flags
& MIXER_GETLINECONTROLSF_QUERYMASK
);
368 return MMSYSERR_INVALPARAM
;
370 TRACE("Returning volume control\n");
371 /* currently, OSS only provides 1 control per line
372 * so one by id == one by type == all
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
;
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
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
;
396 /**************************************************************************
397 * MIX_GetControlDetails [internal]
399 static DWORD
MIX_GetControlDetails(WORD wDevID
, LPMIXERCONTROLDETAILS lpmcd
, DWORD fdwDetails
)
401 DWORD ret
= MMSYSERR_NOTSUPPORTED
;
403 TRACE("(%04X, %p, %lu)\n", wDevID
, lpmcd
, fdwDetails
);
405 if (lpmcd
== NULL
) return MMSYSERR_INVALPARAM
;
407 switch (fdwDetails
& MIXER_GETCONTROLDETAILSF_QUERYMASK
) {
408 case MIXER_GETCONTROLDETAILSF_VALUE
:
409 TRACE("GCD VALUE (%08lx)\n", lpmcd
->dwControlID
);
411 LPMIXERCONTROLDETAILS_UNSIGNED mcdu
;
414 /* ControlID == LineID == OSS mixer channel */
415 /* return value is 00RL (4 bytes)... */
416 if (!MIX_GetVal(lpmcd
->dwControlID
, &val
))
417 return MMSYSERR_INVALPARAM
;
419 switch (lpmcd
->cChannels
) {
421 /* mono... so R = L */
422 mcdu
= (LPMIXERCONTROLDETAILS_UNSIGNED
)lpmcd
->paDetails
;
423 mcdu
->dwValue
= (LOBYTE(LOWORD(val
)) * 65536L) / 100;
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;
433 WARN("Unknown cChannels (%ld)\n", lpmcd
->cChannels
);
434 return MMSYSERR_INVALPARAM
;
436 TRACE("=> %08lx\n", mcdu
->dwValue
);
438 ret
= MMSYSERR_NOERROR
;
440 case MIXER_GETCONTROLDETAILSF_LISTTEXT
:
444 WARN("Unknown flag (%08lx)\n", fdwDetails
& MIXER_GETCONTROLDETAILSF_QUERYMASK
);
449 /**************************************************************************
450 * MIX_SetControlDetails [internal]
452 static DWORD
MIX_SetControlDetails(WORD wDevID
, LPMIXERCONTROLDETAILS lpmcd
, DWORD fdwDetails
)
454 DWORD ret
= MMSYSERR_NOTSUPPORTED
;
456 TRACE("(%04X, %p, %lu)\n", wDevID
, lpmcd
, fdwDetails
);
458 if (lpmcd
== NULL
) return MMSYSERR_INVALPARAM
;
460 switch (fdwDetails
& MIXER_GETCONTROLDETAILSF_QUERYMASK
) {
461 case MIXER_GETCONTROLDETAILSF_VALUE
:
462 TRACE("GCD VALUE (%08lx)\n", lpmcd
->dwControlID
);
464 LPMIXERCONTROLDETAILS_UNSIGNED mcdu
;
467 /* val should contain 00RL */
468 switch (lpmcd
->cChannels
) {
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);
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;
485 WARN("Unknown cChannels (%ld)\n", lpmcd
->cChannels
);
486 return MMSYSERR_INVALPARAM
;
489 /* ControlID == LineID == OSS mixer channel */
490 if (!MIX_SetVal(lpmcd
->dwControlID
, &val
))
491 return MMSYSERR_INVALPARAM
;
493 ret
= MMSYSERR_NOERROR
;
495 case MIXER_GETCONTROLDETAILSF_LISTTEXT
:
499 WARN("Unknown GetControlDetails flag (%08lx)\n", fdwDetails
& MIXER_GETCONTROLDETAILSF_QUERYMASK
);
501 return MMSYSERR_NOTSUPPORTED
;
504 #endif /* HAVE_OSS */
506 /**************************************************************************
507 * mixMessage [sample driver]
509 DWORD WINAPI
mixMessage(WORD wDevID
, WORD wMsg
, DWORD dwUser
,
510 DWORD dwParam1
, DWORD dwParam2
)
512 TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n",
513 wDevID
, wMsg
, dwUser
, dwParam1
, dwParam2
);
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");
525 return MIX_Open(wDevID
, (LPMIXEROPENDESC
)dwParam1
, dwParam2
);
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
);
535 WARN("unknown message %d!\n", wMsg
);
537 return MMSYSERR_NOTSUPPORTED
;
539 return MMSYSERR_NOTENABLED
;