OSS mixer fixes

eric pouech eric.pouech at wanadoo.fr
Thu Nov 1 15:00:18 CST 2001


this patch fixes several issues found in mixer code
- the DST_WAVE_IN wasn't properly handled
- lots of error cases were not checked
- all requests mode weren't supported
- the record part (and settings) wasn't handled

all of this should be better know (at least 3 programs which were broken
before are now fixed, or at least they display reasonable results)

A+
-- 
---------------
Eric Pouech (http://perso.wanadoo.fr/eric.pouech/)
"The future will be better tomorrow", Vice President Dan Quayle
-------------- next part --------------
Name: mixer
ChangeLog: fixed dst / src lines implementation
	added mux/mixer control for master rec
	added framework for multiple mixers
GenDate: 2001/11/01 20:57:57 UTC
ModifiedFiles: dlls/winmm/wineoss/mixer.c
AddedFiles: 
===================================================================
RCS file: /usr/share/cvs/cvsroot/wine/wine/dlls/winmm/wineoss/mixer.c,v
retrieving revision 1.13
diff -u -u -r1.13 mixer.c
--- dlls/winmm/wineoss/mixer.c	2001/07/11 18:56:45	1.13
+++ dlls/winmm/wineoss/mixer.c	2001/11/01 20:57:53
@@ -4,7 +4,7 @@
  * Sample MIXER Wine Driver for Linux
  *
  * Copyright 	1997 Marcus Meissner
- * 		1999 Eric Pouech
+ * 		1999,2001 Eric Pouech
  */
 
 #include "config.h"
@@ -15,6 +15,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <assert.h>
 #include <sys/ioctl.h>
 #include "windef.h"
 #include "mmddk.h"
@@ -25,8 +26,6 @@
 
 #ifdef HAVE_OSS
 
-#define MIXER_DEV "/dev/mixer"
-
 #define	WINE_MIXER_MANUF_ID		0xAA
 #define	WINE_MIXER_PRODUCT_ID		0x55
 #define	WINE_MIXER_VERSION		0x0100
@@ -35,34 +34,263 @@
 #define WINE_CHN_MASK(_x)		(1L << (_x))
 #define WINE_CHN_SUPPORTS(_c, _x)	((_c) & WINE_CHN_MASK(_x))
 /* Bass and Treble are no longer in the mask as Windows does not handle them */
-#define WINE_MIXER_MASK			(WINE_CHN_MASK(SOUND_MIXER_VOLUME) | \
-                                         WINE_CHN_MASK(SOUND_MIXER_SYNTH)  | \
+#define WINE_MIXER_MASK_SPEAKER		(WINE_CHN_MASK(SOUND_MIXER_SYNTH)  | \
                                          WINE_CHN_MASK(SOUND_MIXER_PCM)    | \
                                          WINE_CHN_MASK(SOUND_MIXER_LINE)   | \
+                                         WINE_CHN_MASK(SOUND_MIXER_MIC)    | \
+                                         WINE_CHN_MASK(SOUND_MIXER_CD)     )
+
+#define WINE_MIXER_MASK_RECORD		(WINE_CHN_MASK(SOUND_MIXER_SYNTH)  | \
+                                         WINE_CHN_MASK(SOUND_MIXER_LINE)   | \
                                          WINE_CHN_MASK(SOUND_MIXER_MIC)    | \
-                                         WINE_CHN_MASK(SOUND_MIXER_CD))
+                                         WINE_CHN_MASK(SOUND_MIXER_IMIX)   )
 
 /* FIXME: the two following string arrays should be moved to a resource file in a string table */
 /* if it's done, better use a struct to hold labels, name, and muted channel volume cache */
 static char*	MIX_Labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
 static char*	MIX_Names [SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
-static int	MIX_Volume[SOUND_MIXER_NRDEVICES];
-static int	MIX_DevMask = 0;
-static int	MIX_StereoMask = 0;
 
+struct mixerCtrl
+{
+    DWORD		dwLineID;
+    MIXERCONTROLA	ctrl;
+};
+
+struct mixer 
+{
+    const char*		name;
+    int			volume[SOUND_MIXER_NRDEVICES];
+    int			devMask;
+    int			stereoMask;
+    int			recMask;
+    BOOL		singleRecChannel;
+    struct mixerCtrl*	ctrl;
+    int			numCtrl;
+};
+
+#define LINEID_DST	0xFFFF
+#define LINEID_SPEAKER	0x0000
+#define LINEID_RECORD	0x0001
+
+static int		MIX_NumMixers;
+static struct mixer	MIX_Mixers[1];
+
 /**************************************************************************
+ * 				MIX_FillLineControls		[internal]
+ */
+static void MIX_FillLineControls(struct mixer* mix, int c, DWORD lineID, DWORD dwType)
+{
+    struct mixerCtrl* 	mc = &mix->ctrl[c];
+    int			j;
+
+    mc->dwLineID = lineID;
+    mc->ctrl.cbStruct = sizeof(MIXERCONTROLA);
+    mc->ctrl.dwControlID = c + 1;
+    mc->ctrl.dwControlType = dwType;
+    
+    switch (dwType) 
+    {
+    case MIXERCONTROL_CONTROLTYPE_VOLUME:
+	mc->ctrl.fdwControl = 0;
+	mc->ctrl.cMultipleItems = 0;
+	lstrcpynA(mc->ctrl.szShortName, "Vol", MIXER_SHORT_NAME_CHARS);
+	lstrcpynA(mc->ctrl.szName, "Volume", MIXER_LONG_NAME_CHARS);
+	memset(&mc->ctrl.Bounds, 0, sizeof(mc->ctrl.Bounds));
+	/* CONTROLTYPE_VOLUME uses the MIXER_CONTROLDETAILS_UNSIGNED struct, 
+	 * [0, 100] is the range supported by OSS
+	 * whatever the min and max values are they must match
+	 * conversions done in (Get|Set)ControlDetails to stay in [0, 100] range
+	 */
+	mc->ctrl.Bounds.s1.dwMinimum = 0;
+	mc->ctrl.Bounds.s1.dwMaximum = 65535;
+	memset(&mc->ctrl.Metrics, 0, sizeof(mc->ctrl.Metrics));
+	break;
+    case MIXERCONTROL_CONTROLTYPE_MUTE:
+    case MIXERCONTROL_CONTROLTYPE_ONOFF:
+	mc->ctrl.fdwControl = 0;
+	mc->ctrl.cMultipleItems = 0;
+	lstrcpynA(mc->ctrl.szShortName, "Mute", MIXER_SHORT_NAME_CHARS);
+	lstrcpynA(mc->ctrl.szName, "Mute", MIXER_LONG_NAME_CHARS);
+	memset(&mc->ctrl.Bounds, 0, sizeof(mc->ctrl.Bounds));
+	mc->ctrl.Bounds.s1.dwMinimum = 0;
+	mc->ctrl.Bounds.s1.dwMaximum = 1;
+	memset(&mc->ctrl.Metrics, 0, sizeof(mc->ctrl.Metrics));
+	break;
+    case MIXERCONTROL_CONTROLTYPE_MUX:
+    case MIXERCONTROL_CONTROLTYPE_MIXER:
+	mc->ctrl.fdwControl = MIXERCONTROL_CONTROLF_MULTIPLE;
+	mc->ctrl.cMultipleItems = 0;
+	for (j = 0; j < SOUND_MIXER_NRDEVICES; j++)
+	    if (WINE_CHN_SUPPORTS(mix->recMask, j))
+		mc->ctrl.cMultipleItems++;
+	lstrcpynA(mc->ctrl.szShortName, "Mixer", MIXER_SHORT_NAME_CHARS);
+	lstrcpynA(mc->ctrl.szName, "Mixer", MIXER_LONG_NAME_CHARS);
+	memset(&mc->ctrl.Bounds, 0, sizeof(mc->ctrl.Bounds));
+	memset(&mc->ctrl.Metrics, 0, sizeof(mc->ctrl.Metrics));
+	break;
+	
+    default:
+	FIXME("Internal error: unknown type: %08lx\n", dwType);
+    }
+    TRACE("ctrl[%2d]: typ=%08lx lin=%08lx\n", c + 1, dwType, lineID);
+}
+
+/******************************************************************
+ *		MIX_GetMixer
+ *
+ *
+ */
+static struct mixer*	MIX_Get(WORD wDevID)
+{
+    if (wDevID >= MIX_NumMixers || MIX_Mixers[wDevID].name == NULL) return NULL;
+    return &MIX_Mixers[wDevID];
+}
+
+/**************************************************************************
+ * 				MIX_Open			[internal]
+ */
+static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD flags)
+{
+    int			mixer, i, j;
+    unsigned 		caps;
+    struct mixer*	mix;
+    DWORD		ret = MMSYSERR_NOERROR;
+
+    TRACE("(%04X, %p, %lu);\n", wDevID, lpMod, flags);
+    
+    /* as we partly init the mixer with MIX_Open, we can allow null open decs */
+    /* EPP     if (lpMod == NULL) return MMSYSERR_INVALPARAM; */
+    /* anyway, it seems that WINMM/MMSYSTEM doesn't always open the mixer device before sending
+     * messages to it... it seems to be linked to all the equivalent of mixer identification
+     * (with a reference to a wave, midi.. handle
+     */
+    if (!(mix = MIX_Get(wDevID))) return MMSYSERR_BADDEVICEID;
+
+    if ((mixer = open(mix->name, O_RDWR)) < 0) 
+    {
+	if (errno == ENODEV || errno == ENXIO) 
+	{	
+	    /* no driver present */
+	    return MMSYSERR_NODRIVER;
+	}
+	return MMSYSERR_ERROR;
+    }
+    
+    if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &mix->devMask) == -1) 
+    {
+	perror("ioctl mixer SOUND_MIXER_DEVMASK");
+	ret = MMSYSERR_ERROR;
+	goto error;
+    }
+    mix->devMask &= WINE_MIXER_MASK_SPEAKER;
+    if (mix->devMask == 0)
+    {
+	ret = MMSYSERR_NODRIVER;
+	goto error;
+    }
+
+    if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &mix->stereoMask) == -1) 
+    {
+	perror("ioctl mixer SOUND_MIXER_STEREODEVS");
+	ret = MMSYSERR_ERROR;
+	goto error;
+    }
+    mix->stereoMask &= WINE_MIXER_MASK_SPEAKER;
+
+    if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &mix->recMask) == -1) 
+    {
+	perror("ioctl mixer SOUND_MIXER_RECMASK");
+	ret = MMSYSERR_ERROR;
+	goto error;
+    }
+    mix->recMask &= WINE_MIXER_MASK_RECORD;
+    /* FIXME: we may need to support both rec lev & igain */
+    if (!WINE_CHN_SUPPORTS(mix->recMask, SOUND_MIXER_RECLEV))
+    {
+	WARN("The sound card doesn't support rec level\n");
+	if (WINE_CHN_SUPPORTS(mix->recMask, SOUND_MIXER_IGAIN))
+	    WARN("but it does support IGain, please report\n");
+    }
+    if (ioctl(mixer, SOUND_MIXER_READ_CAPS, &caps) == -1) 
+    {
+	perror("ioctl mixer SOUND_MIXER_READ_CAPS");
+	ret = MMSYSERR_ERROR;
+	goto error;
+    }
+    mix->singleRecChannel = caps & SOUND_CAP_EXCL_INPUT;
+    TRACE("dev=%04x rec=%04x stereo=%04x %s\n", 
+	  mix->devMask, mix->recMask, mix->stereoMask, 
+	  mix->singleRecChannel ? "single" : "multiple");
+    for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) 
+    {
+	mix->volume[i] = -1;
+    }
+    mix->numCtrl = 4; /* dst lines... vol&mute on speakers, vol&onoff on rec */
+    /* FIXME: do we always have RECLEV on all cards ??? */
+    for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+    {
+	if (WINE_CHN_SUPPORTS(mix->devMask, i))
+	    mix->numCtrl += 2; /* volume & mute */
+	if (WINE_CHN_SUPPORTS(mix->recMask, i))
+	    mix->numCtrl += 2; /* volume & onoff */
+	
+    }
+    if (!(mix->ctrl = HeapAlloc(GetProcessHeap(), 0, sizeof(mix->ctrl[0]) * mix->numCtrl)))
+    {
+	ret = MMSYSERR_NOMEM;
+	goto error;
+    }
+    
+    j = 0;
+    MIX_FillLineControls(mix, j++, MAKELONG(0, LINEID_DST), MIXERCONTROL_CONTROLTYPE_VOLUME);
+    MIX_FillLineControls(mix, j++, MAKELONG(0, LINEID_DST), MIXERCONTROL_CONTROLTYPE_MUTE);
+    MIX_FillLineControls(mix, j++, MAKELONG(1, LINEID_DST), 
+			 mix->singleRecChannel ?
+			    MIXERCONTROL_CONTROLTYPE_MUX : MIXERCONTROL_CONTROLTYPE_MIXER);
+    MIX_FillLineControls(mix, j++, MAKELONG(1, LINEID_DST), MIXERCONTROL_CONTROLTYPE_MUTE/*EPP*/);
+    for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+    {
+	if (WINE_CHN_SUPPORTS(mix->devMask, i))
+	{
+	    MIX_FillLineControls(mix, j++, MAKELONG(LINEID_SPEAKER, i),
+				 MIXERCONTROL_CONTROLTYPE_VOLUME);
+	    MIX_FillLineControls(mix, j++, MAKELONG(LINEID_SPEAKER, i),
+				 MIXERCONTROL_CONTROLTYPE_MUTE);
+	}
+    }
+    for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
+    {
+	if (WINE_CHN_SUPPORTS(mix->recMask, i))
+	{
+	    MIX_FillLineControls(mix, j++, MAKELONG(LINEID_RECORD, i),
+				 MIXERCONTROL_CONTROLTYPE_VOLUME);
+	    MIX_FillLineControls(mix, j++, MAKELONG(LINEID_RECORD, i),
+				 MIXERCONTROL_CONTROLTYPE_MUTE/*EPP*/);
+	}
+    }
+    assert(j == mix->numCtrl);
+ error:
+    close(mixer);
+    return ret;
+}
+
+/**************************************************************************
  * 				MIX_GetVal			[internal]
  */
-static	BOOL	MIX_GetVal(int chn, int* val)
+static	BOOL	MIX_GetVal(struct mixer* mix, int chn, int* val)
 {
     int		mixer;
     BOOL	ret = FALSE;
 
-    if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
+    if ((mixer = open(mix->name, O_RDWR)) < 0) 
+    {
 	/* FIXME: ENXIO => no mixer installed */
 	WARN("mixer device not available !\n");
-    } else {
-	if (ioctl(mixer, MIXER_READ(chn), val) >= 0) {
+    } 
+    else 
+    {
+	if (ioctl(mixer, MIXER_READ(chn), val) >= 0) 
+	{
 	    TRACE("Reading volume %x on %d\n", *val, chn);
 	    ret = TRUE;
 	}
@@ -74,18 +302,22 @@
 /**************************************************************************
  * 				MIX_SetVal			[internal]
  */
-static	BOOL	MIX_SetVal(int chn, int val)
+static	BOOL	MIX_SetVal(struct mixer* mix, int chn, int val)
 {
     int		mixer;
     BOOL	ret = FALSE;
 
     TRACE("Writing volume %x on %d\n", val, chn);
 
-    if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
+    if ((mixer = open(mix->name, O_RDWR)) < 0) 
+    {
 	/* FIXME: ENXIO => no mixer installed */
 	WARN("mixer device not available !\n");
-    } else {
-	if (ioctl(mixer, MIXER_WRITE(chn), &val) >= 0) {
+    }
+    else 
+    {
+	if (ioctl(mixer, MIXER_WRITE(chn), &val) >= 0) 
+	{
 	    ret = TRUE;
 	}
 	close(mixer);
@@ -93,40 +325,136 @@
     return ret;
 }
 
+/******************************************************************
+ *		MIX_GetRecSrc
+ *
+ *
+ */
+static BOOL	MIX_GetRecSrc(struct mixer* mix, unsigned* mask)
+{
+    int		mixer;
+    BOOL	ret = FALSE;
+
+    if ((mixer = open(mix->name, O_RDWR)) >= 0)
+    {
+	if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &mask) >= 0) ret = TRUE;
+	close(mixer);
+    }
+    return ret;
+}
+
+/******************************************************************
+ *		MIX_SetRecSrc
+ *
+ *
+ */
+static BOOL	MIX_SetRecSrc(struct mixer* mix, unsigned mask)
+{
+    int		mixer;
+    BOOL	ret = FALSE;
+
+    if ((mixer = open(mix->name, O_RDWR)) >= 0) 
+    {
+	if (ioctl(mixer, SOUND_MIXER_WRITE_RECSRC, &mask) < 0) 
+	{
+	    ERR("Can't write new mixer settings\n");
+	}
+	else
+	    ret = TRUE;
+	close(mixer);
+    }
+    return ret;
+}
+
 /**************************************************************************
  * 				MIX_GetDevCaps			[internal]
  */
 static DWORD MIX_GetDevCaps(WORD wDevID, LPMIXERCAPSA lpCaps, DWORD dwSize)
 {
+    struct mixer*	mix;
+
     TRACE("(%04X, %p, %lu);\n", wDevID, lpCaps, dwSize);
     
-    if (wDevID != 0) return MMSYSERR_BADDEVICEID;
     if (lpCaps == NULL) return MMSYSERR_INVALPARAM;
-    if (!MIX_DevMask) return MMSYSERR_NOTENABLED;
+    if (!(mix = MIX_Get(wDevID))) return MMSYSERR_BADDEVICEID;
 
     lpCaps->wMid = WINE_MIXER_MANUF_ID;
     lpCaps->wPid = WINE_MIXER_PRODUCT_ID;
     lpCaps->vDriverVersion = WINE_MIXER_VERSION;
     strcpy(lpCaps->szPname, WINE_MIXER_NAME);
 
-    lpCaps->cDestinations = 1;
+    lpCaps->cDestinations = 2; /* speakers & record */
     lpCaps->fdwSupport = 0; /* No bits defined yet */
     
     return MMSYSERR_NOERROR;
 }
 
+/**************************************************************************
+ * 				MIX_GetLineInfoDst	[internal]
+ */
+static	DWORD	MIX_GetLineInfoDst(struct mixer* mix, LPMIXERLINEA lpMl, DWORD dst)
+{	
+    unsigned mask;
+    int	j;
+
+    lpMl->dwDestination = dst;
+    switch (dst)
+    {
+    case 0:
+	lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
+	mask = mix->devMask;
+	j = SOUND_MIXER_VOLUME;
+	break;
+    case 1:
+	lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
+	mask = mix->recMask;
+	j = SOUND_MIXER_RECLEV;
+	break;
+    default:
+	FIXME("shouldn't happen\n");
+	return MMSYSERR_ERROR;
+    }
+    lpMl->dwSource = 0xFFFFFFFF;
+    lstrcpynA(lpMl->szShortName, MIX_Labels[j], MIXER_SHORT_NAME_CHARS);
+    lstrcpynA(lpMl->szName, MIX_Names[j], MIXER_LONG_NAME_CHARS);
+	
+    /* we have all connections found in the MIX_DevMask */
+    lpMl->cConnections = 0;
+    for (j = 0; j < SOUND_MIXER_NRDEVICES; j++)
+	if (WINE_CHN_SUPPORTS(mask, j))
+	    lpMl->cConnections++;
+    lpMl->cChannels = 1;
+    if (WINE_CHN_SUPPORTS(mix->stereoMask, lpMl->dwLineID))
+	lpMl->cChannels++;
+    lpMl->dwLineID = MAKELONG(dst, LINEID_DST);
+    lpMl->cControls = 0;
+    for (j = 0; j < mix->numCtrl; j++)
+	if (mix->ctrl[j].dwLineID == lpMl->dwLineID)
+	    lpMl->cControls++;
+
+    return MMSYSERR_NOERROR;
+}
+
 /**************************************************************************
- * 				MIX_GetLineInfoFromIndex	[internal]
+ * 				MIX_GetLineInfoSrc	[internal]
  */
-static	DWORD	MIX_GetLineInfoFromIndex(LPMIXERLINEA lpMl, int devmask, DWORD idx)
+static	DWORD	MIX_GetLineInfoSrc(struct mixer* mix, LPMIXERLINEA lpMl, DWORD idx, DWORD dst)
 {
+    int		i, j;
+    unsigned	mask = (dst) ? mix->recMask : mix->devMask;
+
     strcpy(lpMl->szShortName, MIX_Labels[idx]);
     strcpy(lpMl->szName, MIX_Names[idx]);
-    lpMl->dwLineID = idx;
-    lpMl->dwDestination = 0; /* index for speakers */
+    lpMl->dwLineID = MAKELONG(dst, idx);
+    lpMl->dwDestination = dst;
     lpMl->cConnections = 1;
-    lpMl->cControls = 2;
-    switch (idx) {
+    lpMl->cControls = 0;
+    for (i = 0; i < mix->numCtrl; i++)
+	if (mix->ctrl[i].dwLineID == lpMl->dwLineID)
+	    lpMl->cControls++;
+
+    switch (idx) 
+    {
     case SOUND_MIXER_SYNTH:
 	lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER;
 	lpMl->fdwLine	 |= MIXERLINE_LINEF_SOURCE;
@@ -147,127 +475,132 @@
 	lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
 	lpMl->fdwLine	 |= MIXERLINE_LINEF_SOURCE;
 	break;
+    case SOUND_MIXER_IMIX:
+	lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
+	lpMl->fdwLine	 |= MIXERLINE_LINEF_SOURCE;
+	break;
     default:
 	WARN("Index %ld not handled.\n", idx);
 	return MIXERR_INVALLINE;
     }
+    lpMl->cChannels = 1;
+    if (dst == 0 && WINE_CHN_SUPPORTS(mix->stereoMask, idx))
+	lpMl->cChannels++;
+    for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++) 
+    {
+	if (WINE_CHN_SUPPORTS(mask, j))
+	{
+	    if (j == idx) break;
+	    i++;
+	}
+    }	
+    lpMl->dwSource = i;
     return MMSYSERR_NOERROR;
 }
 
+/******************************************************************
+ *		MIX_CheckLine
+ */
+static BOOL MIX_CheckLine(DWORD lineID)
+{
+    return ((HIWORD(lineID) < SOUND_MIXER_NRDEVICES && LOWORD(lineID) < 2) || 
+	    (HIWORD(lineID) == LINEID_DST && LOWORD(lineID) < SOUND_MIXER_NRDEVICES));
+}
+
 /**************************************************************************
  * 				MIX_GetLineInfo			[internal]
  */
 static DWORD MIX_GetLineInfo(WORD wDevID, LPMIXERLINEA lpMl, DWORD fdwInfo)
 {
     int 		i, j;
-    BOOL		isDst = FALSE;
     DWORD		ret = MMSYSERR_NOERROR;
-    
+    unsigned		mask;
+    struct mixer*	mix;
+
     TRACE("(%04X, %p, %lu);\n", wDevID, lpMl, fdwInfo);
+
     if (lpMl == NULL || lpMl->cbStruct != sizeof(*lpMl)) 
 	return MMSYSERR_INVALPARAM;
+    if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
     
     /* FIXME: set all the variables correctly... the lines below
      * are very wrong...
      */
     lpMl->fdwLine	= MIXERLINE_LINEF_ACTIVE;
-    lpMl->cChannels	= 1;
     lpMl->dwUser	= 0;
-    lpMl->cControls	= 2;
     
-    switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK) {
+    switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK) 
+    {
     case MIXER_GETLINEINFOF_DESTINATION:
 	TRACE("DESTINATION (%08lx)\n", lpMl->dwDestination);
-	/* FIXME: Linux doesn't seem to support multiple outputs? 
-	 * So we have only one output type: Speaker.
-	 */
-	lpMl->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
-	lpMl->dwSource = 0xFFFFFFFF;
-	lpMl->dwLineID = SOUND_MIXER_VOLUME;
-	lstrcpynA(lpMl->szShortName, MIX_Labels[SOUND_MIXER_VOLUME], MIXER_SHORT_NAME_CHARS);
-	lstrcpynA(lpMl->szName, MIX_Names[SOUND_MIXER_VOLUME], MIXER_LONG_NAME_CHARS);
-	
-	/* we have all connections found in the MIX_DevMask */
-	lpMl->cConnections = 0;
-	for (j = 1; j < SOUND_MIXER_NRDEVICES; j++)
-	    if (WINE_CHN_SUPPORTS(MIX_DevMask, j))
-		lpMl->cConnections++;
-	if (WINE_CHN_SUPPORTS(MIX_StereoMask, SOUND_MIXER_VOLUME))
-	    lpMl->cChannels++;
+	if (lpMl->dwDestination >= 2)
+	    return MMSYSERR_INVALPARAM;
+	if ((ret = MIX_GetLineInfoDst(mix, lpMl, lpMl->dwDestination)) != MMSYSERR_NOERROR)
+	    return ret;
 	break;
     case MIXER_GETLINEINFOF_SOURCE:
-	TRACE("SOURCE (%08lx)\n", lpMl->dwSource);
+	TRACE("SOURCE (%08lx), dst=%08lx\n", lpMl->dwSource, lpMl->dwDestination);
+	switch (lpMl->dwDestination)
+	{
+	case 0: mask = mix->devMask; break;
+	case 1: mask = mix->recMask; break;
+	default: return MMSYSERR_INVALPARAM;
+	}
 	i = lpMl->dwSource;
-	for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
-	    if (WINE_CHN_SUPPORTS(MIX_DevMask, j) && (i-- == 0))
+	for (j = 0; j < SOUND_MIXER_NRDEVICES; j++) 
+	{
+	    if (WINE_CHN_SUPPORTS(mask, j) && (i-- == 0))
 		break;
 	}
 	if (j >= SOUND_MIXER_NRDEVICES)
 	    return MIXERR_INVALLINE;
-	if (WINE_CHN_SUPPORTS(MIX_StereoMask, j))
-	    lpMl->cChannels++;
-	if ((ret = MIX_GetLineInfoFromIndex(lpMl, MIX_DevMask, j)) != MMSYSERR_NOERROR)
+	if ((ret = MIX_GetLineInfoSrc(mix, lpMl, j, lpMl->dwDestination)) != MMSYSERR_NOERROR)
 	    return ret;
 	break;
     case MIXER_GETLINEINFOF_LINEID:
 	TRACE("LINEID (%08lx)\n", lpMl->dwLineID);
-	if (lpMl->dwLineID >= SOUND_MIXER_NRDEVICES)
+
+	if (!MIX_CheckLine(lpMl->dwLineID))
 	    return MIXERR_INVALLINE;
-	if (WINE_CHN_SUPPORTS(MIX_StereoMask, lpMl->dwLineID))
-	    lpMl->cChannels++;
-	if ((ret = MIX_GetLineInfoFromIndex(lpMl, MIX_DevMask, lpMl->dwLineID)) != MMSYSERR_NOERROR)
+	if (HIWORD(lpMl->dwLineID) == LINEID_DST)
+	    ret = MIX_GetLineInfoDst(mix, lpMl, LOWORD(lpMl->dwLineID));
+	else
+	    ret = MIX_GetLineInfoSrc(mix, lpMl, HIWORD(lpMl->dwLineID), LOWORD(lpMl->dwLineID));
+	if (ret != MMSYSERR_NOERROR)
 	    return ret;
 	break;
     case MIXER_GETLINEINFOF_COMPONENTTYPE:
 	TRACE("COMPONENT TYPE (%08lx)\n", lpMl->dwComponentType);
-	
-	switch (lpMl->dwComponentType) {
+	switch (lpMl->dwComponentType) 
+	{
 	case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
-	    i = SOUND_MIXER_VOLUME;
-	    lpMl->dwDestination = 0;
-	    lpMl->dwSource = 0xFFFFFFFF;
-	    isDst = TRUE;
+	    ret = MIX_GetLineInfoDst(mix, lpMl, 0);
+	    break;
+	case MIXERLINE_COMPONENTTYPE_DST_WAVEIN:
+	    ret = MIX_GetLineInfoDst(mix, lpMl, 1);
 	    break;
 	case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER:
-	    i = SOUND_MIXER_SYNTH;
-	    lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
+	    ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_SYNTH, 0);
 	    break;
 	case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC:
-	    i = SOUND_MIXER_CD;
-	    lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
+	    ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_CD, 0);
 	    break;
 	case MIXERLINE_COMPONENTTYPE_SRC_LINE:
-	    i = SOUND_MIXER_LINE;
-	    lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
+	    ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_LINE, 0);
 	    break;
 	case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE:
-	    i = SOUND_MIXER_MIC;
-	    lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
+	    ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_MIC, 1);
 	    break;
 	case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT:
-	    i = SOUND_MIXER_PCM;
-	    lpMl->fdwLine |= MIXERLINE_LINEF_SOURCE;
+	    ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_PCM, 0);
 	    break;
+	case MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED:
+	    ret = MIX_GetLineInfoSrc(mix, lpMl, SOUND_MIXER_IMIX, 1);
+	    break;
 	default:
 	    FIXME("Unhandled component type (%08lx)\n", lpMl->dwComponentType);
 	    return MMSYSERR_INVALPARAM;
 	}
-	
-	if (WINE_CHN_SUPPORTS(MIX_DevMask, i)) {
-	    strcpy(lpMl->szShortName, MIX_Labels[i]);
-	    strcpy(lpMl->szName, MIX_Names[i]);
-	    lpMl->dwLineID = i;
-	}
-	if (WINE_CHN_SUPPORTS(MIX_StereoMask, i))
-	    lpMl->cChannels++;
-	lpMl->cConnections = 0;
-	if (isDst) {
-	    for (j = 1; j < SOUND_MIXER_NRDEVICES; j++) {
-		if (WINE_CHN_SUPPORTS(MIX_DevMask, j)) {
-		    lpMl->cConnections++;
-		}
-	    }
-	}
 	break;
     case MIXER_GETLINEINFOF_TARGETTYPE:
 	FIXME("_TARGETTYPE not implemented yet.\n");
@@ -277,7 +610,7 @@
 	break;
     }
     
-    lpMl->Target.dwType = MIXERLINE_TARGETTYPE_AUX;
+    lpMl->Target.dwType = MIXERLINE_TARGETTYPE_AUX; /* FIXME */
     lpMl->Target.dwDeviceID = 0xFFFFFFFF;
     lpMl->Target.wMid = WINE_MIXER_MANUF_ID;
     lpMl->Target.wPid = WINE_MIXER_PRODUCT_ID;
@@ -286,85 +619,14 @@
     
     return ret;
 }
-
-/**************************************************************************
- * 				MIX_GetLineInfo			[internal]
- */
-static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD flags)
-{
-    TRACE("(%04X, %p, %lu);\n", wDevID, lpMod, flags);
-    if (lpMod == NULL) return MMSYSERR_INVALPARAM;
-    
-    return (MIX_DevMask == 0) ? MMSYSERR_NODRIVER : MMSYSERR_NOERROR;
-}
-
-/**************************************************************************
- * 				MIX_MakeControlID		[internal]
- */
-static DWORD MIX_MakeControlID(DWORD lineID, DWORD controlType)
-{
-    switch (controlType) {
-    case MIXERCONTROL_CONTROLTYPE_VOLUME:
-	return 2 * lineID + 0;
-    case MIXERCONTROL_CONTROLTYPE_MUTE:
-	return 2 * lineID + 1;
-    }
-    FIXME("Internal error");
-    return 0x00FADE00;
-}
-
-/**************************************************************************
- * 				MIX_SplitControlID		[internal]
- */
-static BOOL MIX_SplitControlID(DWORD controlID, LPDWORD lineID, LPDWORD controlType)
-{
-    *lineID = controlID / 2;
-    *controlType = (controlID & 1) ? 
-	MIXERCONTROL_CONTROLTYPE_MUTE : MIXERCONTROL_CONTROLTYPE_VOLUME;
-
-    return *lineID < SOUND_MIXER_NRDEVICES && WINE_CHN_SUPPORTS(MIX_DevMask, *lineID);
-}
 
-/**************************************************************************
- * 				MIX_DoGetLineControls		[internal]
+/******************************************************************
+ *		MIX_CheckControl
+ *
  */
-static void MIX_DoGetLineControls(LPMIXERCONTROLA mc, DWORD lineID, DWORD dwType)
+static BOOL	MIX_CheckControl(struct mixer* mix, DWORD ctrlID)
 {
-    mc->cbStruct = sizeof(MIXERCONTROLA);
-    
-    switch (dwType) {
-    case MIXERCONTROL_CONTROLTYPE_VOLUME:
-	TRACE("Returning volume control\n");
-	mc->dwControlID = MIX_MakeControlID(lineID, dwType);
-	mc->dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
-	mc->fdwControl = 0;
-	mc->cMultipleItems = 0;
-	lstrcpynA(mc->szShortName, "Vol", MIXER_SHORT_NAME_CHARS);
-	lstrcpynA(mc->szName, "Volume", MIXER_LONG_NAME_CHARS);
-	memset(&mc->Bounds, 0, sizeof(mc->Bounds));
-	/* CONTROLTYPE_VOLUME uses the MIXER_CONTROLDETAILS_UNSIGNED struct, 
-	 * [0, 100] is the range supported by OSS
-	 * whatever the min and max values are they must match
-	 * conversions done in (Get|Set)ControlDetails to stay in [0, 100] range
-	 */
-	mc->Bounds.s1.dwMinimum = 0;
-	mc->Bounds.s1.dwMaximum = 65535;
-	memset(&mc->Metrics, 0, sizeof(mc->Metrics));
-	break;
-    case MIXERCONTROL_CONTROLTYPE_MUTE:
-	TRACE("Returning mute control\n");
-	mc->dwControlID = MIX_MakeControlID(lineID, dwType);
-	mc->dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
-	mc->fdwControl = 0;
-	mc->cMultipleItems = 0;
-	lstrcpynA(mc->szShortName, "Mute", MIXER_SHORT_NAME_CHARS);
-	lstrcpynA(mc->szName, "Mute", MIXER_LONG_NAME_CHARS);
-	memset(&mc->Bounds, 0, sizeof(mc->Bounds));
-	memset(&mc->Metrics, 0, sizeof(mc->Metrics));
-	break;
-    default:
-	FIXME("Internal error: unknown type: %08lx\n", dwType);
-    }
+    return (ctrlID >= 1 && ctrlID <= mix->numCtrl);
 }
 
 /**************************************************************************
@@ -373,7 +635,7 @@
 static	DWORD	MIX_GetLineControls(WORD wDevID, LPMIXERLINECONTROLSA lpMlc, DWORD flags)
 {
     DWORD		dwRet = MMSYSERR_NOERROR;
-    DWORD		lineID, controlType;
+    struct mixer*	mix;
 
     TRACE("(%04X, %p, %lu);\n", wDevID, lpMlc, flags);
     
@@ -381,35 +643,63 @@
     if (lpMlc->cbStruct < sizeof(*lpMlc) ||
 	lpMlc->cbmxctrl < sizeof(MIXERCONTROLA))
 	return MMSYSERR_INVALPARAM;
+    if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
 
-    switch (flags & MIXER_GETLINECONTROLSF_QUERYMASK) {
+    switch (flags & MIXER_GETLINECONTROLSF_QUERYMASK) 
+    {
     case MIXER_GETLINECONTROLSF_ALL:
-	TRACE("line=%08lx GLCF_ALL (%ld)\n", lpMlc->dwLineID, lpMlc->cControls);
-	if (lpMlc->cControls != 2) {
-	    dwRet = MMSYSERR_INVALPARAM;
-	} else {
-	    MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME);
-	    MIX_DoGetLineControls(&lpMlc->pamxctrl[1], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_MUTE);
+        {
+	    int		i, j;
+
+	    TRACE("line=%08lx GLCF_ALL (%ld)\n", lpMlc->dwLineID, lpMlc->cControls);
+	
+	    for (i = j = 0; i < mix->numCtrl; i++)
+	    {
+		if (mix->ctrl[i].dwLineID == lpMlc->dwLineID)
+		    j++;
+	    }
+	    if (!j || lpMlc->cControls != j)		dwRet = MMSYSERR_INVALPARAM;
+	    else if (!MIX_CheckLine(lpMlc->dwLineID))	dwRet = MIXERR_INVALLINE;
+	    else 
+	    {
+		for (i = j = 0; i < mix->numCtrl; i++)
+		{
+		    if (mix->ctrl[i].dwLineID == lpMlc->dwLineID)
+		    {
+			TRACE("[%d] => [%2d]: typ=%08lx\n", j, i + 1, mix->ctrl[i].ctrl.dwControlType);
+			lpMlc->pamxctrl[j++] = mix->ctrl[i].ctrl;
+		    }
+		}
+	    }
 	}
 	break;
     case MIXER_GETLINECONTROLSF_ONEBYID:
 	TRACE("line=%08lx GLCF_ONEBYID (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlID);
-	if (MIX_SplitControlID(lpMlc->u.dwControlID, &lineID, &controlType))
-	    MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lineID, controlType);
-	else 
+
+	if (!MIX_CheckControl(mix, lpMlc->u.dwControlID) ||
+	    mix->ctrl[lpMlc->u.dwControlID - 1].dwLineID != lpMlc->dwLineID)
 	    dwRet = MMSYSERR_INVALPARAM;
+	else
+	    lpMlc->pamxctrl[0] = mix->ctrl[lpMlc->u.dwControlID - 1].ctrl;
 	break;
     case MIXER_GETLINECONTROLSF_ONEBYTYPE:
 	TRACE("line=%08lx GLCF_ONEBYTYPE (%lx)\n", lpMlc->dwLineID, lpMlc->u.dwControlType);
-	switch (lpMlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK) {
-	case MIXERCONTROL_CT_CLASS_FADER:
-	    MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_VOLUME);
-	    break;
-	case MIXERCONTROL_CT_CLASS_SWITCH:
-	    MIX_DoGetLineControls(&lpMlc->pamxctrl[0], lpMlc->dwLineID, MIXERCONTROL_CONTROLTYPE_MUTE);
-	    break;
-	default:
-	    dwRet = MMSYSERR_INVALPARAM;
+	if (!MIX_CheckLine(lpMlc->dwLineID))	dwRet = MIXERR_INVALLINE;
+	else 
+	{
+	    int		i;
+	    DWORD	ct = lpMlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK;
+
+	    for (i = 0; i < mix->numCtrl; i++)
+	    {
+		if (mix->ctrl[i].dwLineID == lpMlc->dwLineID &&
+		    ct == (mix->ctrl[i].ctrl.dwControlType & MIXERCONTROL_CT_CLASS_MASK))
+		{
+		    lpMlc->pamxctrl[0] = mix->ctrl[i].ctrl;
+		    break;
+		}
+	    }
+	    if (i == mix->numCtrl) dwRet = MMSYSERR_INVALPARAM;
 	}
 	break;
     default:
@@ -425,28 +715,39 @@
  */
 static	DWORD	MIX_GetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
 {
-    DWORD	ret = MMSYSERR_NOTSUPPORTED;
-    DWORD	lineID, controlType;
-
+    DWORD		ret = MMSYSERR_NOTSUPPORTED;
+    DWORD		c, chnl;
+    struct mixer*	mix;
+    
     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
     
     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
+    if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
 
-    switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
+    switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) 
+    {
     case MIXER_GETCONTROLDETAILSF_VALUE:
 	TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
-	if (MIX_SplitControlID(lpmcd->dwControlID, &lineID, &controlType)) {
-	    switch (controlType) {
+	if (MIX_CheckControl(mix, lpmcd->dwControlID))
+	{
+	    c = lpmcd->dwControlID - 1;
+	    chnl = HIWORD(mix->ctrl[c].dwLineID);
+	    if (chnl == LINEID_DST) 
+		chnl = LOWORD(mix->ctrl[c].dwLineID) ? SOUND_MIXER_RECLEV : SOUND_MIXER_VOLUME;
+	    switch (mix->ctrl[c].ctrl.dwControlType) 
+	    {
 	    case MIXERCONTROL_CONTROLTYPE_VOLUME:
 		{
 		    LPMIXERCONTROLDETAILS_UNSIGNED	mcdu;
 		    int					val;
 
+		    TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_UNSIGNED), lpmcd->cbDetails);
 		    /* return value is 00RL (4 bytes)... */
-		    if ((val = MIX_Volume[lineID]) == -1 && !MIX_GetVal(lineID, &val))
+		    if ((val = mix->volume[chnl]) == -1 && !MIX_GetVal(mix, chnl, &val))
 			return MMSYSERR_INVALPARAM;
 	    
-		    switch (lpmcd->cChannels) {
+		    switch (lpmcd->cChannels) 
+		    {
 		    case 1:
 			/* mono... so R = L */
 			mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
@@ -467,22 +768,88 @@
 		}
 		break;
 	    case MIXERCONTROL_CONTROLTYPE_MUTE:
+	    case MIXERCONTROL_CONTROLTYPE_ONOFF:
 		{
 		    LPMIXERCONTROLDETAILS_BOOLEAN	mcdb;
 		    
+		    TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
 		    /* we mute both channels at the same time */
 		    mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
-		    mcdb->fValue = (MIX_Volume[lineID] != -1);
+		    mcdb->fValue = (mix->volume[chnl] != -1);
+		    TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
+		}
+		break;
+	    case MIXERCONTROL_CONTROLTYPE_MIXER:
+	    case MIXERCONTROL_CONTROLTYPE_MUX:
+		{
+		    unsigned				mask;
+
+		    TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
+		    if (!MIX_GetRecSrc(mix, &mask))
+		    {
+			/* FIXME: ENXIO => no mixer installed */
+			WARN("mixer device not available !\n");
+			ret = MMSYSERR_ERROR;
+		    }
+		    else 
+		    {
+			LPMIXERCONTROLDETAILS_BOOLEAN	mcdb;
+			int				i, j;
+
+			/* we mute both channels at the same time */
+			mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
+
+			for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++)
+			{
+			    if (WINE_CHN_SUPPORTS(mix->recMask, j))
+			    {
+				if (i >= lpmcd->u.cMultipleItems) 
+				    return MMSYSERR_INVALPARAM;
+				mcdb[i++].fValue = WINE_CHN_SUPPORTS(mask, j);
+			    }
+			}
+		    }
 		}
 		break;
+	    default:
+		WARN("Unsupported\n");
 	    }
 	    ret = MMSYSERR_NOERROR;
-	} else {
+	}
+	else 
+	{
 	    ret = MMSYSERR_INVALPARAM;
 	}
 	break;
     case MIXER_GETCONTROLDETAILSF_LISTTEXT:
-	FIXME("NIY\n");
+	TRACE("LIST TEXT (%08lx)\n", lpmcd->dwControlID);
+
+	ret = MMSYSERR_INVALPARAM;
+	if (MIX_CheckControl(mix, lpmcd->dwControlID))
+	{
+	    int	c = lpmcd->dwControlID - 1;
+
+	    if (mix->ctrl[c].ctrl.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX ||
+		mix->ctrl[c].ctrl.dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER)
+	    {
+		LPMIXERCONTROLDETAILS_LISTTEXTA	mcdlt;
+		int i, j;
+
+		mcdlt = (LPMIXERCONTROLDETAILS_LISTTEXTA)lpmcd->paDetails;
+		for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++)
+		{
+		    if (WINE_CHN_SUPPORTS(mix->recMask, j))
+		    {
+			mcdlt[i].dwParam1 = MAKELONG(LINEID_RECORD, j);
+			mcdlt[i].dwParam2 = 0;
+			strcpy(mcdlt[i].szName, MIX_Names[j]);
+			i++;
+		    }
+		}
+		if (i != lpmcd->u.cMultipleItems) FIXME("bad count\n");
+		ret = MMSYSERR_NOERROR;
+	    }
+	}
 	break;
     default:
 	WARN("Unknown flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
@@ -495,25 +862,37 @@
  */
 static	DWORD	MIX_SetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD fdwDetails)
 {
-    DWORD	ret = MMSYSERR_NOTSUPPORTED;
-    DWORD	lineID, controlType;
-    int		val;
+    DWORD		ret = MMSYSERR_NOTSUPPORTED;
+    DWORD		c, chnl;
+    int			val;
+    struct mixer*	mix;
     
     TRACE("(%04X, %p, %lu);\n", wDevID, lpmcd, fdwDetails);
     
     if (lpmcd == NULL) return MMSYSERR_INVALPARAM;
+    if ((mix = MIX_Get(wDevID)) == NULL) return MMSYSERR_BADDEVICEID;
     
-    switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
+    switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) 
+    {
     case MIXER_GETCONTROLDETAILSF_VALUE:
 	TRACE("GCD VALUE (%08lx)\n", lpmcd->dwControlID);
-	if (MIX_SplitControlID(lpmcd->dwControlID, &lineID, &controlType)) {
-	    switch (controlType) {
+	if (MIX_CheckControl(mix, lpmcd->dwControlID))
+	{
+	    c = lpmcd->dwControlID - 1;
+	    chnl = HIWORD(mix->ctrl[c].dwLineID);
+	    if (chnl == LINEID_DST) 
+		chnl = LOWORD(mix->ctrl[c].dwLineID) ? SOUND_MIXER_RECLEV : SOUND_MIXER_VOLUME;
+
+	    switch (mix->ctrl[c].ctrl.dwControlType) 
+	    {
 	    case MIXERCONTROL_CONTROLTYPE_VOLUME:
 		{
 		    LPMIXERCONTROLDETAILS_UNSIGNED	mcdu;
 		    
+		    TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_UNSIGNED), lpmcd->cbDetails);
 		    /* val should contain 00RL */
-		    switch (lpmcd->cChannels) {
+		    switch (lpmcd->cChannels) 
+		    {
 		    case 1:
 			/* mono... so R = L */
 			mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
@@ -534,55 +913,83 @@
 			return MMSYSERR_INVALPARAM;
 		    }
 		    
-		    if (MIX_Volume[lineID] == -1) {
-			if (!MIX_SetVal(lineID, val))
+		    if (mix->volume[chnl] == -1) 
+		    {
+			if (!MIX_SetVal(mix, chnl, val))
 			    return MMSYSERR_INVALPARAM;
-		    } else {
-			MIX_Volume[lineID] = val;
 		    }
+		    else 
+		    {
+			mix->volume[chnl] = val;
+		    }
 		}
 		ret = MMSYSERR_NOERROR;
 		break;
 	    case MIXERCONTROL_CONTROLTYPE_MUTE:
+	    case MIXERCONTROL_CONTROLTYPE_ONOFF:
 		{
 		    LPMIXERCONTROLDETAILS_BOOLEAN	mcdb;
 		    
+		    TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
 		    mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
-		    if (mcdb->fValue) {
-			if (!MIX_GetVal(lineID, &MIX_Volume[lineID]))
-			    return MMSYSERR_INVALPARAM;
-			if (!MIX_SetVal(lineID, 0))
+		    if (mcdb->fValue) 
+		    {
+			if (!MIX_GetVal(mix, chnl, &mix->volume[chnl]) || !MIX_SetVal(mix, chnl, 0))
 			    return MMSYSERR_INVALPARAM;
-		    } else {
-			if (MIX_Volume[lineID] == -1) {
-				ret = MMSYSERR_NOERROR;
-				break;
+		    }	
+		    else 
+		    {
+			if (mix->volume[chnl] == -1) 
+			{
+			    ret = MMSYSERR_NOERROR;
+			    break;
 			}
-			if (!MIX_SetVal(lineID, MIX_Volume[lineID]))
+			if (!MIX_SetVal(mix, chnl, mix->volume[chnl]))
 			    return MMSYSERR_INVALPARAM;
-			MIX_Volume[lineID] = -1;
+			mix->volume[chnl] = -1;
 		    }
 		}
 		ret = MMSYSERR_NOERROR;
 		break;
+	    case MIXERCONTROL_CONTROLTYPE_MIXER:
+	    case MIXERCONTROL_CONTROLTYPE_MUX:
+		{
+		    LPMIXERCONTROLDETAILS_BOOLEAN	mcdb;
+		    unsigned				mask;
+		    int					i, j;
+
+		    TRACE(" <> %u %lu\n", sizeof(MIXERCONTROLDETAILS_BOOLEAN), lpmcd->cbDetails);
+		    /* we mute both channels at the same time */
+		    mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
+		    mask = 0;
+		    for (i = j = 0; j < SOUND_MIXER_NRDEVICES; j++)
+		    {
+			if (WINE_CHN_SUPPORTS(mix->recMask, j) && mcdb[i++].fValue)
+			{
+			    /* a mux can only select one line at a time... */
+			    if (mix->singleRecChannel && mask != 0)	
+			    {
+				FIXME("!!!\n");
+				return MMSYSERR_INVALPARAM;
+			    }
+			    mask |= WINE_CHN_MASK(j);
+			}
+		    }
+		    if (i != lpmcd->u.cMultipleItems) FIXME("bad count\n");
+		    TRACE("writing %04x as rec src\n", mask);
+		    if (!MIX_SetRecSrc(mix, mask)) 
+			ERR("Can't write new mixer settings\n");
+		    else
+			ret = MMSYSERR_NOERROR;
+		}
+		break;
 	    }
 	}
 	break;
-    case MIXER_GETCONTROLDETAILSF_LISTTEXT:
-	FIXME("NIY\n");
-	break;
     default:
-	WARN("Unknown GetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
+	WARN("Unknown SetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
     }
-    return (ret);
-}
-
-/**************************************************************************
- * 				MIX_GetNumDevs			[internal]
- */
-static	DWORD	MIX_GetNumDevs(UINT wDevID)
-{
-    return (MIX_DevMask != 0) ? 1 : 0;
+    return ret;
 }
 
 /**************************************************************************
@@ -590,51 +997,35 @@
  */
 static	DWORD	MIX_Init(void)
 {
-    int	mixer, i;
+    int	mixer;
 
-    if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
-	if (errno == ENODEV || errno == ENXIO) {	
+#define MIXER_DEV "/dev/mixer"
+    if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) 
+    {
+	if (errno == ENODEV || errno == ENXIO) 
+	{	
 	    /* no driver present */
 	    return MMSYSERR_NODRIVER;
 	}
+	MIX_NumMixers = 0;
 	return MMSYSERR_ERROR;
     }
-    if (ioctl(mixer, SOUND_MIXER_READ_DEVMASK, &MIX_DevMask) == -1) {
-	close(mixer);
-	perror("ioctl mixer SOUND_MIXER_DEVMASK");
-	return MMSYSERR_NOTENABLED;
-    }
-    MIX_DevMask &= WINE_MIXER_MASK;
-    if (ioctl(mixer, SOUND_MIXER_READ_STEREODEVS, &MIX_StereoMask) == -1) {
-	close(mixer);
-	perror("ioctl mixer SOUND_MIXER_STEREODEVS");
-	return MMSYSERR_NOTENABLED;
-    }
-    MIX_StereoMask &= WINE_MIXER_MASK;
-
-#if 0
-    int			recsrc, recmask;
-
-    if (ioctl(mixer, SOUND_MIXER_READ_RECSRC, &recsrc) == -1) {
-	close(mixer);
-	perror("ioctl mixer SOUND_MIXER_RECSRC");
-	return MMSYSERR_NOTENABLED;
-    }
-
-    if (ioctl(mixer, SOUND_MIXER_READ_RECMASK, &recmask) == -1) {
-	close(mixer);
-	perror("ioctl mixer SOUND_MIXER_RECMASK");
-	return MMSYSERR_NOTENABLED;
-    }
-#endif
-    for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
-	MIX_Volume[i] = -1;
-    }
     close(mixer);
-
+    MIX_NumMixers = 1;
+    MIX_Mixers[0].name = MIXER_DEV;
+    MIX_Open(0, NULL, 0); /* FIXME */
+#undef MIXER_DEV
     return MMSYSERR_NOERROR;
 }
 
+/**************************************************************************
+ * 				MIX_GetNumDevs			[internal]
+ */
+static	DWORD	MIX_GetNumDevs(void)
+{
+    return MIX_NumMixers;
+}
+
 #endif /* HAVE_OSS */
 
 /**************************************************************************
@@ -643,14 +1034,13 @@
 DWORD WINAPI OSS_mixMessage(UINT wDevID, UINT wMsg, DWORD dwUser, 
 			    DWORD dwParam1, DWORD dwParam2)
 {
-    TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", 
-	  wDevID, wMsg, dwUser, dwParam1, dwParam2);
+/* TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2); */
     
 #ifdef HAVE_OSS
-    switch(wMsg) {
+    switch (wMsg) 
+    {
     case DRVM_INIT:
 	return MIX_Init();
-	break; 
     case DRVM_EXIT:
     case DRVM_ENABLE:
     case DRVM_DISABLE:
@@ -661,9 +1051,10 @@
     case MXDM_GETLINEINFO:
 	return MIX_GetLineInfo(wDevID, (LPMIXERLINEA)dwParam1, dwParam2);
     case MXDM_GETNUMDEVS:
-	return MIX_GetNumDevs(wDevID);
+	return MIX_GetNumDevs();
     case MXDM_OPEN:
-	return MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2);
+	return MMSYSERR_NOERROR;
+	/* MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2); */
     case MXDM_CLOSE:
 	return MMSYSERR_NOERROR;
     case MXDM_GETLINECONTROLS:
@@ -674,8 +1065,8 @@
 	return MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
     default:
 	WARN("unknown message %d!\n", wMsg);
+	return MMSYSERR_NOTSUPPORTED;
     }
-    return MMSYSERR_NOTSUPPORTED;
 #else
     return MMSYSERR_NOTENABLED;
 #endif


More information about the wine-patches mailing list