/***************************************************************************
*   NAME:  MXD.C $Revision: 1.20 $
**  COPYRIGHT:
**  "Copyright (c) 1994,1995 by e-Tek Labs"
**
**  "This software is furnished under a license and may be used,
**   copied, or disclosed only in accordance with the terms of such
**   license and with the inclusion of the above copyright notice.
**   This software or any other copies thereof may not be provided or
**   otherwise made available to any other person. No title to and
**   ownership of the software is hereby transfered."
****************************************************************************
* $Log: mxd.c $
* Revision 1.20  1995/11/28 10:52:52  teckert
* Changed init code to prevent multiple sequential opens and to allow the 
* mixer to run concurrently with a DOS box app.
* Revision 1.19  1995/11/10 15:00:35  sdsmith
* Changes for Win95 compatability
* Revision 1.18  1995/10/31 15:28:22  mks
* Revision 1.17  1995/10/30 16:00:36  sdsmith
* Win95 Compatability test fixes
* Revision 1.16  1995/10/24 15:34:15  unknown
* Changes for Voyetra
* Revision 1.15  1995/10/24 11:26:37  sdsmith
* Fixes for Windows 95 compatability tests
* Revision 1.14  1995/09/21 16:41:26  unknown
* Added checks for LineID to get Windows 95 built in mixer to work properly.
* Revision 1.13  1995/09/05 15:55:34  teckert
* Added PnP style device capabilities structure.
* Revision 1.12  1995/08/09 17:01:35  sdsmith
* Corrections for mixer bugs
* Revision 1.11  1995/07/06 19:07:09  sdsmith
* Additions for Save Settings
* Revision 1.10  1995/05/29 17:32:50  sdsmith
* Various bug fixes
* Revision 1.9  1995/05/03 16:03:17  teckert
* Added check for stereo volume when calling iw_linear_to_db
* Revision 1.8  1995/04/19 15:40:32  sdsmith
* Various fixes
* Revision 1.7  1995/04/18 17:27:24  sdsmith
* New mixer support
* Revision 1.6  1995/04/04 15:44:39  teckert
* Added viwd.h to include file list and fixed spelling error in line 154
* Revision 1.5  1995/04/04 15:41:50  teckert
* Un-commented the hardware acquisition code
* Revision 1.4  1995/03/30 10:10:40  sdsmith
* Added new mixer support
* Revision 1.3  1995/03/17 13:01:31  teckert
* Correction made to HWAllocate call (returns -1 for error not 0)
* Revision 1.2  1995/03/16 15:48:06  teckert
* Added hardware acquisition code
* Revision 1.1  1995/02/23 15:15:15  sdsmith
* Initial revision
***************************************************************************/
#pragma optimize("",off)
#include <dos.h>
#include <windows.h>
#include <mmsystem.h>
#include <mmddk.h>
#include "iw.h"
#include "interwav.h"
#include <viwd.h>

/* indices into the multiplexer array for controls */
#define SOURCE_LINE  0
#define SOURCE_MIC   1
#define SOURCE_SYNTH 2
#define SOURCE_MIX   3

/****************************************************************************

LOCAL DEFINITION:
MIXERCLIENT - structure for mixer client information

DESCRIPTION:
  hMix       - client's handle to the mixer device
  dwCallback - address of a callback function, window handle, or task
			   handle
  uFlags     - flag indicating type of address in dwCallback
  dwInstance - client's instance information to be returned to the 
			   client whenever DriverCallback is called
  wHWAH      - hardware acquisition handle (returned from HWAquire)

*/
typedef struct mixer_client_s {
    HMIXER hMix;
    DWORD dwCallback;
    UINT  uFlags;
    DWORD dwInstance;
    WORD  wHWAH;
    struct mixer_client_s *next;
}
MIXERCLIENT, *PMIXERCLIENT, FAR *LPMIXERCLIENT;

MIXERCAPS mxcaps = {
    MM_ETEK, MM_INTERWAVE_MIXER, DRIVER_VERSION
};

extern int num_outputs; /* total number of outputs in the mixer */
extern LPMIXEROUTPUT outputs;
extern LPMIXERNODE find_node(short, iw_mixer_node_type, short);

/****************************************************************************

LOCAL DEFINITION:
pMxdClients - head of list of clients which have opened the mixer device

*/
static PMIXERCLIENT pMxdClient = 0;


/****************************************************************************

FUNCTION DEFINITION:
mxdOpen - open the mixer device for a new client

DESCRIPTION:
This routine handles the MXDM_OPEN message. The information for the
caller is stored into a linked list to track which clients have opened
the mixer device.

See the Microsoft Windows Sound System Driver Development kit for
details regarding this message.

RETURNS: DWORD -
  MMSYSERR_NOERROR
  MMSYSERR_NOMEM - the was no memory available to log the new client

*/
DWORD mxdOpen(
  LPDWORD lpdwUser,
  LPMIXEROPENDESC mxdOpenDesc,
  DWORD dwFlags)
{
    DWORD rc = MMSYSERR_NOERROR;
    LPMIXERCLIENT new_client;
    HLOCAL hClient;

    if (pMxdClient == 0) {
	hClient = LocalAlloc(GHND, sizeof(MIXERCLIENT));
	new_client = (PMIXERCLIENT)LocalLock(hClient);

	if (new_client) {
//            if ((new_client->wHWAH = HWAllocate(IWAR_CODECMIX))==-1) {
//                LocalFree(LocalHandle((UINT)new_client));
//                return MMSYSERR_ALLOCATED;
//            }
	    new_client->hMix = mxdOpenDesc->hmx;
	    new_client->dwCallback = mxdOpenDesc->dwCallback;
	    new_client->dwInstance = mxdOpenDesc->dwInstance;
	    switch (dwFlags & CALLBACK_WINDOW|CALLBACK_FUNCTION|CALLBACK_TASK) {
		case CALLBACK_WINDOW:
		    new_client->uFlags = DCB_WINDOW;
		    break;
		case CALLBACK_FUNCTION:
		    new_client->uFlags = DCB_FUNCTION;
		    break;
		case CALLBACK_TASK:
		    new_client->uFlags = DCB_TASK;
		    break;
	    }
	    new_client->next = 0;
	    pMxdClient = new_client;

	    *lpdwUser = (DWORD)new_client;
	}
	else {
	    rc = MMSYSERR_NOMEM;
	}
    }
    else {
	rc = MMSYSERR_ALLOCATED;
    }
    return(rc);
}

DWORD mxdClose(
  DWORD dwUser)
{
    PMIXERCLIENT client, tmp;

    if (pMxdClient) {
//        HWFree(((PMIXERCLIENT)pMxdClient)->wHWAH);
        if(pMxdClient == (PMIXERCLIENT)dwUser){
            pMxdClient = ((PMIXERCLIENT)dwUser)->next;
	        LocalFree(LocalHandle((UINT)dwUser));
        }else{   
            client = pMxdClient;
            while (client && client->next != (PMIXERCLIENT)dwUser) client = client->next;
            if(client){
                client->next = client->next->next;
                LocalFree(LocalHandle((UINT)dwUser));
            }
        }
    }
}

void mxdLineChange(int state, int line)
{
    int i,j;
    DWORD old,new,dwLineID;
    PMIXERCLIENT tmp;

    old = 0;
    new = 0;
    switch (line) {
	case MM_INTERWAVE_WAVEOUT:
	    for (i=0; i<num_outputs; i++) {
		for (j=0; j<outputs[i].mxl.cConnections; j++) {
		    if (outputs[i].inputs[j].mxl.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT) {
			dwLineID = outputs[i].inputs[j].mxl.dwLineID;
			old = outputs[i].inputs[j].mxl.fdwLine;
			if (state)
			    outputs[i].inputs[j].mxl.fdwLine |= MIXERLINE_LINEF_ACTIVE;
			else
			    outputs[i].inputs[j].mxl.fdwLine &= ~MIXERLINE_LINEF_ACTIVE;
			new = outputs[i].inputs[j].mxl.fdwLine;
		    }
		}
	    }
	    break;
	case MM_INTERWAVE_WAVEIN:
	    for (i=0; i<num_outputs; i++) {
		if (outputs[i].mxl.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_WAVEIN) {
		    dwLineID = outputs[i].mxl.dwLineID;
		    old = outputs[i].mxl.fdwLine;
		    if (state)
			outputs[i].mxl.fdwLine |= MIXERLINE_LINEF_ACTIVE;
		    else
			outputs[i].mxl.fdwLine &= ~MIXERLINE_LINEF_ACTIVE;
		    new = outputs[i].mxl.fdwLine;
		}
	    }
	    break;
	case MM_INTERWAVE_MIDIOUT:
	    for (i=0; i<num_outputs; i++) {
		for (j=0; j<outputs[i].mxl.cConnections; j++) {
		    if (outputs[i].inputs[j].mxl.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER) {
			dwLineID = outputs[i].inputs[j].mxl.dwLineID;
			old = outputs[i].inputs[j].mxl.fdwLine;
			if (state)
			    outputs[i].inputs[j].mxl.fdwLine |= MIXERLINE_LINEF_ACTIVE;
			else
			    outputs[i].inputs[j].mxl.fdwLine &= ~MIXERLINE_LINEF_ACTIVE;
			new = outputs[i].inputs[j].mxl.fdwLine;
		    }
		}
	    }
	    break;
	default:
	    break;
    }
    if (old != new) {
	tmp = pMxdClient;
	while (tmp) {
	    DriverCallback( tmp -> dwCallback,
			    tmp -> uFlags,
			    tmp -> hMix,
			    MM_MIXM_LINE_CHANGE,
			    tmp -> dwInstance,
			    dwLineID,
			    0L) ;

	    tmp = tmp->next ;
	}
    }
}

/****************************************************************************

FUNCTION DEFINITION:
mxdGetLineInfo - get the information for a mixer line

DESCRIPTION:
This routine handles the MXDM_GETLINEINFO message. The information for 
the mixer line specified in the lpmxl parameter is retrieved based on 
the flags specified by dwFlags.

See the Microsoft Windows Sound System Driver Development kit for
details regarding this message.

COMMENTS:
This driver does not support the MIXER_LINEINFOF_COMPONENTTYPE or
MIXER_LINEINFOF_TARGETTYPE even though it is supposed to.  These can
be added if necessary later.  Since applications assume these messages
are supported, we will simply return MIXERR_INVALLINE instead of
MMSYSERR_NOTSUPPORTED.

RETURNS: DWORD -
  MMSYSERR_NOERROR
  MMSYSERR_NOTSUPPORTED
  MIXERR_INVALLINE

*/
DWORD mxdGetLineInfo(
  LPMIXERLINE lpmxl,
  DWORD dwFlags)
{
    DWORD rc = MIXERR_INVALLINE;
    int i,j;
    UINT mdev,devs;
    WAVEOUTCAPS wodcaps;
    WAVEINCAPS widcaps;
    MIDIOUTCAPS modcaps;

    switch (dwFlags & MIXER_GETLINEINFOF_QUERYMASK) {
	case MIXER_GETLINEINFOF_DESTINATION:
	    for (i=0; i<num_outputs; i++) {
		if (outputs[i].mxl.dwDestination == lpmxl->dwDestination) {
		    _fmemcpy(lpmxl, &(outputs[i].mxl), min(lpmxl->cbStruct,sizeof(MIXERLINE)));
		    rc = MMSYSERR_NOERROR;
		    if (lpmxl->dwComponentType == MIXERLINE_COMPONENTTYPE_DST_WAVEIN) {
			devs = (int)waveInGetNumDevs();
			if (devs) {
			    for (mdev=0; mdev<devs; mdev++) {
				if (waveInGetDevCaps(mdev, &widcaps, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR) {
				    if (widcaps.wMid == MM_ETEK && widcaps.wPid == MM_INTERWAVE_WAVEIN) {
					lpmxl->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
					lpmxl->Target.dwDeviceID = (DWORD)mdev;
					lpmxl->Target.wMid = widcaps.wMid;
					lpmxl->Target.wPid = widcaps.wPid;
					lpmxl->Target.vDriverVersion = widcaps.vDriverVersion;
					lstrcpy(lpmxl->Target.szPname, widcaps.szPname);
				    }
				}
			    }
			}
		    }
		    break;
		}
	    }
	    break;
	case MIXER_GETLINEINFOF_SOURCE:
	    for (i=0; i<num_outputs; i++) {
		if (outputs[i].mxl.dwDestination == lpmxl->dwDestination) {
		    for (j=0; j<outputs[i].mxl.cConnections; j++) {
			if (outputs[i].inputs[j].mxl.dwSource == lpmxl->dwSource) {
			    _fmemcpy(lpmxl, &(outputs[i].inputs[j].mxl), min(lpmxl->cbStruct,sizeof(MIXERLINE)));
			    rc = MMSYSERR_NOERROR;
			    switch (lpmxl->dwComponentType) {
				case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT:
				    devs = (int)waveOutGetNumDevs();
				    if (devs) {
					for (mdev=0; mdev<devs; mdev++) {
					    if (waveOutGetDevCaps(mdev, &wodcaps, sizeof(WAVEOUTCAPS)) == MMSYSERR_NOERROR) {
						if (wodcaps.wMid == MM_ETEK && wodcaps.wPid == MM_INTERWAVE_WAVEOUT) {
						    lpmxl->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
						    lpmxl->Target.dwDeviceID = (DWORD)mdev;
						    lpmxl->Target.wMid = wodcaps.wMid;
						    lpmxl->Target.wPid = wodcaps.wPid;
						    lpmxl->Target.vDriverVersion = wodcaps.vDriverVersion;
						    lstrcpy(lpmxl->Target.szPname, wodcaps.szPname);
						}
					    }
					}
				    }
				    break;
				case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER:
				    devs = (int)midiOutGetNumDevs();
				    if (devs) {
					for (mdev=0; mdev<devs; mdev++) {
					    if (midiOutGetDevCaps(mdev, &modcaps, sizeof(MIDIOUTCAPS)) == MMSYSERR_NOERROR) {
						if (modcaps.wMid == MM_ETEK && modcaps.wPid == MM_INTERWAVE_MIDISYNTH) {
						    lpmxl->Target.dwType = MIXERLINE_TARGETTYPE_MIDIOUT;
						    lpmxl->Target.dwDeviceID = (DWORD)mdev;
						    lpmxl->Target.wMid = modcaps.wMid;
						    lpmxl->Target.wPid = modcaps.wPid;
						    lpmxl->Target.vDriverVersion = modcaps.vDriverVersion;
						    lstrcpy(lpmxl->Target.szPname, modcaps.szPname);
						}
					    }
					}
				    }
				    break;
				default:
				    break;
			    }
			    break;
			}
		    }
		    break;
		}
	    }
	    break;
	case MIXER_GETLINEINFOF_LINEID:
	    for (i=0; i<num_outputs; i++) {
		if (outputs[i].mxl.dwLineID == lpmxl->dwLineID) {
		    _fmemcpy(lpmxl, &(outputs[i].mxl), min(lpmxl->cbStruct,sizeof(MIXERLINE)));
		    rc = MMSYSERR_NOERROR;
		    break;
		}
	    }
	    if (rc != MMSYSERR_NOERROR) {
		for (i=0; i<num_outputs; i++) {
		    for (j=0; j<outputs[i].mxl.cConnections; j++) {
			if (outputs[i].inputs[j].mxl.dwLineID == lpmxl->dwLineID) {
			    _fmemcpy(lpmxl, &(outputs[i].inputs[j].mxl), min(lpmxl->cbStruct,sizeof(MIXERLINE)));
			    rc = MMSYSERR_NOERROR;
			    break;
			}
		    }
		    if (rc == MMSYSERR_NOERROR)
			break;
		}
	    }
	    break;
	case MIXER_GETLINEINFOF_COMPONENTTYPE:
	    for (i=0; i<num_outputs; i++) {
		if (outputs[i].mxl.dwComponentType == lpmxl->dwComponentType) {
		    _fmemcpy(lpmxl, &(outputs[i].mxl), min(lpmxl->cbStruct,sizeof(MIXERLINE)));
		    rc = MMSYSERR_NOERROR;
		    break;
		}
	    }
	    if (rc != MMSYSERR_NOERROR) {
		for (i=0; i<num_outputs; i++) {
		    for (j=0; j<outputs[i].mxl.cConnections; j++) {
			if (outputs[i].inputs[j].mxl.dwComponentType == lpmxl->dwComponentType) {
			    _fmemcpy(lpmxl, &(outputs[i].inputs[j].mxl), min(lpmxl->cbStruct,sizeof(MIXERLINE)));
			    rc = MMSYSERR_NOERROR;
			    break;
			}
		    }
		    if (rc == MMSYSERR_NOERROR)
			break;
		}
	    }
	    break;
	case MIXER_GETLINEINFOF_TARGETTYPE:
	    rc = MIXERR_INVALLINE;
	    switch (lpmxl->Target.dwType) {
		case MIXERLINE_TARGETTYPE_WAVEIN:
		    for (i=0; i<num_outputs; i++) {
			if (outputs[i].mxl.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_WAVEIN) {
			    _fmemcpy(lpmxl, &(outputs[i].mxl), min(lpmxl->cbStruct,sizeof(MIXERLINE)));
			    rc = MMSYSERR_NOERROR;
			    devs = (int)waveInGetNumDevs();
			    if (devs) {
				for (mdev=0; mdev<devs; mdev++) {
				    if (waveInGetDevCaps(mdev, &widcaps, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR) {
					if (wodcaps.wMid == MM_ETEK && wodcaps.wPid == MM_INTERWAVE_WAVEIN) {
					    lpmxl->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
					    lpmxl->Target.dwDeviceID = (DWORD)mdev;
					    lpmxl->Target.wMid = widcaps.wMid;
					    lpmxl->Target.wPid = widcaps.wPid;
					    lpmxl->Target.vDriverVersion = widcaps.vDriverVersion;
					    lstrcpy(lpmxl->Target.szPname, widcaps.szPname);
					}
				    }
				}
			    }
			    break;
			}
		    }
		    break;
		case MIXERLINE_TARGETTYPE_WAVEOUT:
		    for (i=0; i<num_outputs; i++) {
			for (j=0; j<outputs[i].mxl.cConnections; j++) {
			    if (outputs[i].inputs[j].mxl.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT) {
				_fmemcpy(lpmxl, &(outputs[i].inputs[j].mxl), min(lpmxl->cbStruct,sizeof(MIXERLINE)));
				rc = MMSYSERR_NOERROR;
				devs = (int)waveOutGetNumDevs();
				if (devs) {
				    for (mdev=0; mdev<devs; mdev++) {
					if (waveOutGetDevCaps(mdev, &wodcaps, sizeof(WAVEOUTCAPS)) == MMSYSERR_NOERROR) {
					    if (wodcaps.wMid == MM_ETEK && wodcaps.wPid == MM_INTERWAVE_WAVEOUT) {
						lpmxl->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
						lpmxl->Target.dwDeviceID = (DWORD)mdev;
						lpmxl->Target.wMid = wodcaps.wMid;
						lpmxl->Target.wPid = wodcaps.wPid;
						lpmxl->Target.vDriverVersion = wodcaps.vDriverVersion;
						lstrcpy(lpmxl->Target.szPname, wodcaps.szPname);
					    }
					}
				    }
				}
				break;
			    }
			}
		    }
		    break;
		case MIXERLINE_TARGETTYPE_MIDIOUT:
		    for (i=0; i<num_outputs; i++) {
			for (j=0; j<outputs[i].mxl.cConnections; j++) {
			    if (outputs[i].inputs[j].mxl.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT) {
				_fmemcpy(lpmxl, &(outputs[i].inputs[j].mxl), min(lpmxl->cbStruct,sizeof(MIXERLINE)));
				rc = MMSYSERR_NOERROR;
				devs = (int)midiOutGetNumDevs();
				if (devs) {
				    for (mdev=0; mdev<devs; mdev++) {
					if (midiOutGetDevCaps(mdev, &modcaps, sizeof(MIDIOUTCAPS)) == MMSYSERR_NOERROR) {
					    if (modcaps.wMid == MM_ETEK && modcaps.wPid == MM_INTERWAVE_MIDISYNTH) {
						lpmxl->Target.dwType = MIXERLINE_TARGETTYPE_MIDIOUT;
						lpmxl->Target.dwDeviceID = (DWORD)mdev;
						lpmxl->Target.wMid = modcaps.wMid;
						lpmxl->Target.wPid = modcaps.wPid;
						lpmxl->Target.vDriverVersion = modcaps.vDriverVersion;
						lstrcpy(lpmxl->Target.szPname, modcaps.szPname);
					    }
					}
				    }
				}
				break;
			    }
			}
		    }
		    break;
		default:
		    break;
	    }
	    break;
	default:
	    rc = MMSYSERR_NOTSUPPORTED;
	    break;
    }
    return(rc);
}

/****************************************************************************

FUNCTION DEFINITION:
mxdGetLineControls - get one or more controls associated with a line

DESCRIPTION:
This routine responds to a MXDM_GETLINECONTROLS message.  This routine 
retrieves one or all of the controls associated with a mixer line based
on the flags specified in the parameter dwFlags.

See the Microsoft Windows Sound System Driver Development kit for
details regarding this message.

RETURNS: DWORD - one of the following:
		   MMSYSERR_NOERROR - operation successful
	   MMSYSERR_NOTENABLED - driver is not initiallized
	   MIXERR_INVALLINE - mixer line specified is invalid
	   MIXERR_INVALCONTROL - control specified is invalid

*/
DWORD mxdGetLineControls(
  LPMIXERLINECONTROLS lpmxlc,
  DWORD               dwFlags)
{
    DWORD rc = MIXERR_INVALCONTROL;
    int i,j,k;

    switch (dwFlags & MIXER_GETLINECONTROLSF_QUERYMASK) {
	case MIXER_GETLINECONTROLSF_ALL:
	    rc = MIXERR_INVALLINE;
	    for (i=0; i<num_outputs; i++) {
		if (outputs[i].mxl.dwLineID == lpmxlc->dwLineID) {
		    _fmemcpy(lpmxlc->pamxctrl, outputs[i].mxlc.pamxctrl, (lpmxlc->cControls * lpmxlc->cbmxctrl));
		    rc = MMSYSERR_NOERROR;
		    break;
		}
		if (rc != MMSYSERR_NOERROR) {
		    for (j=0; j<outputs[i].mxl.cConnections; j++) {
			if (outputs[i].inputs[j].mxl.dwLineID == lpmxlc->dwLineID) {
			    _fmemcpy(lpmxlc->pamxctrl, outputs[i].inputs[j].mxlc.pamxctrl, (lpmxlc->cControls * lpmxlc->cbmxctrl));
			    rc = MMSYSERR_NOERROR;
			    break;
			}
		    }
		}
	    }
	    break;
	case MIXER_GETLINECONTROLSF_ONEBYID:
	    for (i=0; i<num_outputs; i++) {
		for (j=0; j < outputs[i].mxl.cControls; j++) {
		    if (outputs[i].mxlc.pamxctrl[j].dwControlID == lpmxlc->dwControlID) {
			_fmemcpy(lpmxlc->pamxctrl, &(outputs[i].mxlc.pamxctrl[j]), lpmxlc->cbmxctrl);
			lpmxlc->dwLineID = outputs[i].mxl.dwLineID;
			rc = MMSYSERR_NOERROR;
			break;
		    }
		}
		if (rc != MMSYSERR_NOERROR) {
		    for (j=0; j < outputs[i].mxl.cConnections; j++) {
			if (j < outputs[i].mxl.cConnections) {
			    for (k=0; k < outputs[i].inputs[j].mxl.cControls; k++) {
				if (outputs[i].inputs[j].mxlc.pamxctrl[k].dwControlID == lpmxlc->dwControlID) {
				    _fmemcpy(lpmxlc->pamxctrl, &(outputs[i].inputs[j].mxlc.pamxctrl[k]), lpmxlc->cbmxctrl);
				    lpmxlc->dwLineID = outputs[i].inputs[j].mxl.dwLineID;
				    rc = MMSYSERR_NOERROR;
				    break;
				}
			    }
			}
		    }
		}
	    }
	    break;
	case MIXER_GETLINECONTROLSF_ONEBYTYPE:
	    rc = MIXERR_INVALLINE;
	    for (i=0; i<num_outputs; i++) {
		if (outputs[i].mxl.dwLineID == lpmxlc->dwLineID) {
		    rc = MIXERR_INVALCONTROL;
		    for (j=0; j < outputs[i].mxl.cControls; j++) {
			if (outputs[i].mxlc.pamxctrl[j].dwControlType == lpmxlc->dwControlType) {
			    _fmemcpy(lpmxlc->pamxctrl, &(outputs[i].mxlc.pamxctrl[j]), lpmxlc->cbmxctrl);
			    lpmxlc->dwLineID = outputs[i].mxl.dwLineID;
			    rc = MMSYSERR_NOERROR;
			    break;
			}
		    }
		}
		if (rc != MMSYSERR_NOERROR) {
		    for (j=0; j < outputs[i].mxl.cConnections; j++) {
			if (outputs[i].inputs[j].mxl.dwLineID == lpmxlc->dwLineID) {
			    rc = MIXERR_INVALCONTROL;
			    for (k=0; k < outputs[i].inputs[j].mxl.cControls; k++) {
				if (outputs[i].inputs[j].mxlc.pamxctrl[k].dwControlType == lpmxlc->dwControlType) {
				    _fmemcpy(lpmxlc->pamxctrl, &(outputs[i].inputs[j].mxlc.pamxctrl[k]), lpmxlc->cbmxctrl);
				    lpmxlc->dwLineID = outputs[i].inputs[j].mxl.dwLineID;
				    rc = MMSYSERR_NOERROR;
				    break;
				}
			    }
			}
		    }
		}
	    }
	    break;
	default:
	    rc = MMSYSERR_NOTSUPPORTED;
	    break;
    }
    return(rc);
}

/****************************************************************************

FUNCTION DEFINITION:
mxdGetControlDetails - Handles the MXDM_GETCONTROLDETAILS message

DESCRIPTION:
Based on the dwFlags paramter, this routine gets the value of the control
specified in lpmxcd to the state specified.  See the Microsoft Windows
Sound System Driver Development kit for details regarding the
MXDM_GETCONTROLDETAILS message.

RETURNS: DWORD -
  MMSYSERR_NOERROR
  MMSYSERR_NOTSUPPORTED - dwFlags contained an invalid value
  MIXERR_INVALCONTROL - the control id from the caller is invalid

*/
DWORD mxdGetControlDetails(
  LPMIXERCONTROLDETAILS lpmxcd,
  DWORD                 dwFlags)
{
    DWORD rc = MMSYSERR_NOERROR;
    LPMIXERNODE node;
    LPMIXERCONTROLDETAILS_UNSIGNED lpmxcd_u;
    LPMIXERCONTROLDETAILS_BOOLEAN  lpmxcd_f;
    LPMIXERCONTROLDETAILS_LISTTEXT lpmxcd_t;
    int i, j, k, l;
    int left, right;

    node = find_node((short)lpmxcd->dwControlID, -1, 0);
    if (node) {
	if (rc == MMSYSERR_NOERROR) {
	    iw_mix_get_control(node->id, &left, &right);
	    switch (dwFlags & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
		case MIXER_GETCONTROLDETAILSF_VALUE:
		    switch (node->subtype) {
			case LEVEL:
			case DUMMY:
			    lpmxcd->cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
			    lpmxcd_u = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmxcd->paDetails;
			    if (lpmxcd->cChannels > 1) {
				lpmxcd_u[IW_MIX_LEFT].dwValue = node->left;
				lpmxcd_u[IW_MIX_RIGHT].dwValue = node->right;
			    }
			    else {
				lpmxcd_u[IW_MIX_LEFT].dwValue = max(node->left,node->right);
			    }
			    lpmxcd->cbStruct = sizeof(MIXERCONTROLDETAILS) +
			       (lpmxcd->cChannels * lpmxcd->cbDetails);
			    break;
			case SWITCH:
			    lpmxcd->cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
			    lpmxcd_f = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmxcd->paDetails;
			    lpmxcd_f[IW_MIX_LEFT].fValue = node->left;
			    if (lpmxcd->cChannels > 1)
				lpmxcd_f[IW_MIX_RIGHT].fValue = node->right;
			    else
				lpmxcd_f[IW_MIX_LEFT].fValue = (node->left|node->right);
			    break;
			case MUX:
			    lpmxcd->cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
			    lpmxcd_f = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmxcd->paDetails;
			    for (i=0; i<num_outputs; i++) {
				for (j=0; j<outputs[i].mxl.cControls; j++) {
				    if (outputs[i].mxlc.pamxctrl[j].dwControlID == lpmxcd->dwControlID) {
					for (k=0; k<outputs[i].mxl.cConnections; k++) {
					    if (k < lpmxcd->cMultipleItems) {
						if (HIWORD(outputs[i].inputs[k].mxl.dwLineID) == node->left) {
						    lpmxcd_f[k].fValue = 1;
						    lpmxcd->cChannels = 1;
						}
						else lpmxcd_f[k].fValue = 0;
					    }
					}
				    }
				}
			    }
			    break;
			default:
			    rc = MIXERR_INVALCONTROL;
			    break;
		    }
		    break;
		case MIXER_GETCONTROLDETAILSF_LISTTEXT:
		    switch (node->subtype) {
			case MUX:
			    rc = MIXERR_INVALCONTROL;
			    lpmxcd_t = (LPMIXERCONTROLDETAILS_LISTTEXT)lpmxcd->paDetails;
			    for (i=0; i<num_outputs; i++) {
				for (j=0; j<outputs[i].mxl.cControls; j++) {
				    if (outputs[i].mxlc.pamxctrl[j].dwControlID == lpmxcd->dwControlID) {
					rc = MMSYSERR_NOERROR;
					for (k=0; k<outputs[i].mxl.cConnections; k++) {
					    if (k < lpmxcd->cMultipleItems) {
						lpmxcd_t[k].dwParam1 = outputs[i].inputs[k].mxl.dwLineID;
						lpmxcd_t[k].dwParam2 = outputs[i].inputs[k].mxl.dwComponentType;
						_fmemcpy(lpmxcd_t[k].szName, outputs[i].inputs[k].mxl.szName, MIXER_LONG_NAME_CHARS);
					    }
					}
				    }
				}
			    }
			    lpmxcd->cbDetails = sizeof(MIXERCONTROLDETAILS_LISTTEXT);
			    lpmxcd->cChannels = 0;
			    break;
			default:
			    rc = MIXERR_INVALCONTROL;
			    break;
		    }
		    break;
		default:
		    rc = MMSYSERR_NOTSUPPORTED;
		    break;
	    }
	}
    }
    else {
	rc = MIXERR_INVALCONTROL;
    }
    return(rc);
}

/****************************************************************************

FUNCTION DEFINITION:
mxdSetControlDetails - Handles the MXDM_SETCONTROLDETAILS message

DESCRIPTION:
Based on the dwFlags paramter, this routine sets the value of the control
specified in lpmxcd to the state specified.  See the Microsoft Windows
Sound System Driver Development kit for details regarding the
MXDM_SETCONTROLDETAILS message.

RETURNS: DWORD -
  MMSYSERR_NOERROR
  MMSYSERR_NOTSUPPORTED - dwFlags contained an invalid value
  MIXERR_INVALCONTROL - the control id from the caller is invalid

*/
DWORD mxdSetControlDetails(
  LPMIXERCONTROLDETAILS lpmxcd,
  DWORD                 dwFlags)
{
    DWORD rc = MMSYSERR_NOERROR;
    LPMIXERNODE node;
    LPMIXERCONTROLDETAILS_UNSIGNED lpmxcd_u;
    LPMIXERCONTROLDETAILS_BOOLEAN  lpmxcd_f;
    LPMIXERCONTROLDETAILS_LISTTEXT lpmxcd_t;
    PMIXERCLIENT tmp;
    int i, j, k, l;
    int left, right;

    node = find_node((short)lpmxcd->dwControlID, -1, 0);
    if (node) {
	switch (dwFlags & MIXER_SETCONTROLDETAILSF_QUERYMASK) {
	    case MIXER_SETCONTROLDETAILSF_VALUE:
		switch (node->subtype) {
		    case LEVEL:
		    case DUMMY:
			lpmxcd->cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
			lpmxcd_u = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmxcd->paDetails;
			node->left = lpmxcd_u[IW_MIX_LEFT].dwValue;
			left = (int)(lpmxcd_u[IW_MIX_LEFT].dwValue >> 9);
			if (left == 64) left = 63;
			if (node->attributes & IW_MIX_ATTR_LOG)
			    left = iw_linear_to_db(node->id, left);
			if (node->attributes & IW_MIX_ATTR_STEREO) {
			    node->right = node->left;
			    right = left;
			    if (lpmxcd->cChannels > 1) {
				node->right = lpmxcd_u[IW_MIX_RIGHT].dwValue;
				right = (int)(lpmxcd_u[IW_MIX_RIGHT].dwValue >> 9);
				if (right == 64) right = 63;
				if (node->attributes & IW_MIX_ATTR_LOG)
				    right = iw_linear_to_db(node->id, right);
			    }
			}
			iw_mix_set_control(node->id, left, right);
			break;
		    case SWITCH:
			left = 0;
			lpmxcd->cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
			lpmxcd_f = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmxcd->paDetails;
			node->left = lpmxcd_f[IW_MIX_LEFT].fValue;
			if (lpmxcd->cChannels > 1)
			    node->right = lpmxcd_f[IW_MIX_RIGHT].fValue;
			else
			    node->right = node->left;
			left = (int)(node->left|node->right);
			iw_mix_set_control((short)lpmxcd->dwControlID, left, left);
			break;
		    case MUX:
			lpmxcd->cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
			lpmxcd_f = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmxcd->paDetails;
			for (l=0; l<lpmxcd->cMultipleItems; l++) {
			    if (lpmxcd_f[l].fValue) {
				break;
			    }
			}
			for (i=0; i<num_outputs; i++) {
			    for (j=0; j<outputs[i].mxl.cControls; j++) {
				if (outputs[i].mxlc.pamxctrl[j].dwControlID == lpmxcd->dwControlID) {
				    if (l < lpmxcd->cMultipleItems) {
					left = HIWORD(outputs[i].inputs[l].mxl.dwLineID);
					node->left = left;
					break;
				    }
				    else rc = MIXERR_INVALCONTROL;
				}
			    }
			}
			iw_mix_set_control((short)lpmxcd->dwControlID, (int)left, (int)left);
			break;
		    default:
			rc = MIXERR_INVALCONTROL;
			break;
		}
		break;
	    case MIXER_SETCONTROLDETAILSF_CUSTOM:
		break;
	    default:
		rc = MMSYSERR_NOTSUPPORTED;
		break;
	}
    }
    else {
	rc = MIXERR_INVALCONTROL;
    }

    if (rc == MMSYSERR_NOERROR) {
	tmp = pMxdClient;
	while (tmp) {
	    DriverCallback( tmp -> dwCallback,
			    tmp -> uFlags,
			    tmp -> hMix,
			    MM_MIXM_CONTROL_CHANGE,
			    tmp -> dwInstance,
			    lpmxcd -> dwControlID,
			    0L) ;

	    tmp = tmp->next ;
	}
    }
    return(rc);
}

/****************************************************************************

FUNCTION DEFINITION:
mxdMessage - Entry point for Mixer Manager 

DESCRIPTION:
This routine provides the standard entry point for the Mixer Manager that
comes with the Microsoft Windows Sound System.  The routine responds to
messages from the msmixmgr.dll in response to API calls from applications.

For a complete list of mixer manager messages, see the Microsoft Windows
Sound System Driver Development Kit.

RETURNS: DWORD - one of the following depending on the message:
				 - a number of devices
		 - an error code:
					 MMSYSERR_NOERROR
					 MMSYSERR_NOTSUPPORTED

*/
DWORD FAR PASCAL _loadds mxdMessage(
   UINT  uDevice,   /* Unique device identifier for target mixer device */
   UINT  uMsg,      /* Message being sent to the driver */
   DWORD dwUser,    /* actually a pointer to a DWORD for instance data */
   DWORD dwParam1,  /* Message dependent parameter */
   DWORD dwParam2)  /* Message dependent parameter */
{
    DWORD rc;

    switch (uMsg) {
	case MXDM_GETNUMDEVS:
	    rc = 1;
	    break;
	case MXDM_GETDEVCAPS:
	    if(wIWDriverFlags & IWDF_WINDOWS95) {
		_fmemcpy(((MDEVICECAPSEX FAR *)dwParam1)->pCaps, (LPMIXERCAPS)&mxcaps, min(sizeof(MIXERCAPS),((MDEVICECAPSEX FAR *)dwParam1)->cbSize));
	    }else {
		_fmemcpy((LPMIXERCAPS)dwParam1, (LPMIXERCAPS)&mxcaps, min(sizeof(MIXERCAPS),(int)dwParam2));
	    }
	    rc = MMSYSERR_NOERROR;
	    break;
	case MXDM_INIT:
	    LoadString(ghModule, STR_IWAVE_MIXER, (LPSTR)mxcaps.szPname, MAXPNAMELEN);
	    mxcaps.cDestinations = num_outputs;
	    rc = MMSYSERR_NOERROR;
	    break;
	case MXDM_OPEN:
	    rc = mxdOpen((LPDWORD)dwUser, (LPMIXEROPENDESC)dwParam1, dwParam2);
	    break;
	case MXDM_CLOSE:
	    rc = mxdClose(dwUser);
	    break;
	case MXDM_GETLINEINFO:
	    rc = mxdGetLineInfo((LPMIXERLINE)dwParam1, dwParam2);
	    break;
	case MXDM_GETLINECONTROLS:
	    rc = mxdGetLineControls((LPMIXERLINECONTROLS)dwParam1, dwParam2);
	    break;
	case MXDM_GETCONTROLDETAILS:
	    rc = mxdGetControlDetails((LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
	    break;
	case MXDM_SETCONTROLDETAILS:
	    rc = mxdSetControlDetails((LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
	    break;
	default:
	    rc = MMSYSERR_NOTSUPPORTED;
	    break;
    }
    return(rc);
}
