/* (C) Copyright Microsoft Corporation 1993.  All Rights Reserved */

#include <windows.h>
#include <mmsystem.h>
#include <string.h>
#include "newvol.h"
#include "volume.h"     // for ini file string identifiers
#include "sndcntrl.h"

#ifdef TESTMIX
#include "mixstub.h"
#endif
//
// Globals
//
#define SHOWMUX
int NumberOfDevices = 0;
PVOLUME_CONTROL    Vol = NULL;

UINT               FirstMasterIndex;

/*
 * Profile file, section and key names
 */
TCHAR gszVolumeSection[64];
TCHAR gszProfileFile[MAX_PATH];

DWORD AdjustMaster(WORD v)
{
    DWORD dwResult;

    if (bMuted) {
        return 1;
    }

    dwResult = (v >> 8) + 1;

    return dwResult;
}

//
// Add a control to our list
//
// Note that the G..Ptr macros in windowsx.h are inadequate and incorrect -
// especially for multithreaded systems where stuff can move while it is
// temporarily unlocked.
//

PVOLUME_CONTROL AddNewControl(VOID)
{
    HGLOBAL         hMem;
    PVOLUME_CONTROL pVol;

    if (Vol == NULL) {
        hMem = GlobalAlloc(GHND, sizeof(VOLUME_CONTROL));
        if (hMem == NULL) {
            return NULL;
        } else {
            Vol = GlobalLock(hMem);
            NumberOfDevices = 1;
        }
    } else {
        HGLOBAL hMemOld;

        hMemOld = GlobalHandle((LPVOID)Vol);
        GlobalUnlock(hMemOld);
        hMem = GlobalReAlloc(hMemOld,
                             sizeof(VOLUME_CONTROL) * (NumberOfDevices + 1),
                             GHND);
        if (hMem == NULL) {
            Vol = GlobalLock(hMemOld);
            return NULL;
        }

        Vol = GlobalLock(hMem);
        NumberOfDevices++;
    }
    pVol = Vol + (NumberOfDevices - 1);

    /*
    **  Finish initialization
    */

    pVol->Index          = NumberOfDevices - 1;

    pVol->MixerId        = (HMIXEROBJ)-1;
    pVol->ControlId      = (DWORD)-1;
    pVol->MuxControlId   = (DWORD)-1;
    pVol->MuteControlId  = (DWORD)-1;
    pVol->MuxSelectIndex = (DWORD)-1;

    return pVol;
}

WORD CombineVolume(WORD Master, WORD Slave)
{
   DWORD Result;

   //
   // treat both numbers as 8-bit volumes, and multiply them
   //

   Result = AdjustMaster(Master) * (DWORD)(Slave >> 8);

   return LOWORD(Result);
}

/*
**  Set the device volume.
**
**  The master volume (and mute setting) are simulated here by
**  scaling the individual device volumes if there is no mixer
**  or the mixer doesn't support the settings
*/
BOOL SetDeviceVolume(PVOLUME_CONTROL pVol, DWORD Volume)
{
    DWORD dwMaster;

    /*
    **  Mixer volumes get set when we get the notification
    */

    if (pVol->VolumeType != VolumeTypeMixerControl) {
        pVol->LRVolume = Volume;
    }

   /*
    *  If it's not the master volume we're setting then
    *  combine the setting with the master volume setting
    */

    if (pVol->Type != MasterVolume) {

        /*
        **  Only simulate controls which don't have real master controls
        */

        if (!pVol->NoMasterSimulation) {
            /*
             * if mute is selected, scale the volume by 1 (not 0)
             * as the master volume. This will still result in an
             * inaudible volume, but will allow us to recover the volume setting
             * from the device when this app restarts.
             */
            dwMaster = MasterDevice(FALSE)->LRVolume;

            Volume = CombineVolume(LOWORD(dwMaster),
                                   LOWORD(Volume)) +
                    (CombineVolume(HIWORD(dwMaster),
                                   HIWORD(Volume)) << 16);
        }
    }

    switch (pVol->Type) {
    case MasterVolume:
        {
           int i;
           for (i = 0; i < NumberOfDevices; i++) {
               if (!Vol[i].NoMasterSimulation && Vol[i].Type != MasterVolume) {
                   SetDeviceVolume(&Vol[i], Vol[i].LRVolume);
               }
           }
        }
        if (pVol->VolumeType == VolumeTypeMixerControl) {
            SetMixerVolume(pVol->MixerId,
                           pVol->ControlId,
                           pVol->Stereo,
                           Volume);
        }
        break;

    case AuxVolume:
        auxSetVolume(pVol->id, Volume);
        break;

    case MidiOutVolume:
#if (WINVER >= 0x0400)
        midiOutSetVolume((HMIDIOUT)pVol->id, Volume);
#else
        midiOutSetVolume(pVol->id, Volume);
#endif
        break;

    case WaveOutVolume:
#if (WINVER >= 0x0400)
        waveOutSetVolume((HWAVEOUT)pVol->id, Volume);
#else
        waveOutSetVolume(pVol->id, Volume);
#endif
        break;

    case MixerControlVolume:
        SetMixerVolume(pVol->MixerId,
                       pVol->ControlId,
                       pVol->Stereo,
                       Volume);
        break;

    }

    if (pVol->VolumeType != VolumeTypeMixerControl) {
        /*
        **  Update the slider(s)
        */

        UpdateVolume(pVol);
    }

    return TRUE;
}

/*
 *  Get the volume associated with a mixer device
 */

VOID GetMixerVolume(HMIXEROBJ MixerId, DWORD dwControlId, BOOL Stereo, LPDWORD pVolume)
{
    MIXERCONTROLDETAILS mxd;
    DWORD               Volume[2];

    Volume[0] = 0;
    Volume[1] = 0;

    mxd.cbStruct        = sizeof(mxd);
    mxd.dwControlID     = dwControlId;
    mxd.cChannels       = Stereo ? 2 : 1;
    mxd.cMultipleItems  = 0;
    mxd.cbDetails       = sizeof(DWORD);
    mxd.paDetails       = (LPVOID)Volume;

    mixerGetControlDetails(MixerId, &mxd, MIXER_GETCONTROLDETAILSF_VALUE);

    if (Stereo) {
        *pVolume = (DWORD)MAKELONG(Volume[0], Volume[1]);
    } else {
        *pVolume = (DWORD)MAKELONG(Volume[0], Volume[0]);
    }
}

/*
 *  Set the volume associated with a mixer device
 */

VOID SetMixerVolume(HMIXEROBJ MixerId, DWORD dwControlId, BOOL Stereo, DWORD NewVolume)
{
    MIXERCONTROLDETAILS mxd;
    DWORD               Volume[2];

    Volume[0] = LOWORD(NewVolume);
    Volume[1] = HIWORD(NewVolume);

    mxd.cbStruct        = sizeof(mxd);
    mxd.dwControlID     = dwControlId;
    mxd.cChannels       = Stereo ? 2 : 1;
    mxd.cMultipleItems  = 0;
    mxd.cbDetails       = sizeof(DWORD);
    mxd.paDetails       = (LPVOID)Volume;

    mixerSetControlDetails(MixerId, &mxd, MIXER_SETCONTROLDETAILSF_VALUE);
}


/*
 *  Get the volume for a given device.  Returns the volume
 *  setting packed in a DWORD
 */

DWORD GetDeviceVolume(PVOLUME_CONTROL pVol)
{
    DWORD Volume;
    DWORD Left;
    DWORD Right;
    DWORD dwMaster;
    PVOLUME_CONTROL pMaster;

    //
    // Default if calls fail
    //

    Volume = pVol->LRVolume;

    switch (pVol->Type) {

    case AuxVolume:
        auxGetVolume(pVol->id, &Volume);
        break;

    case MidiOutVolume:
#if (WINVER >= 0x0400)
        midiOutGetVolume((HMIDIOUT)pVol->id, &Volume);
#else
        midiOutGetVolume(pVol->id, &Volume);
#endif
        break;

    case WaveOutVolume:
#if (WINVER >= 0x0400)
        waveOutGetVolume((HWAVEOUT)pVol->id, &Volume);
#else
        waveOutGetVolume(pVol->id, &Volume);
#endif
        break;

    case MixerControlVolume:
    case MasterVolume:

        /*
        ** don't scale by master vol in this case
        */

        if (pVol->VolumeType != VolumeTypeMixerControl) {
            return Volume;
        }

        GetMixerVolume(pVol->MixerId,
                       pVol->ControlId,
                       pVol->Stereo,
                       &Volume);

        if (pVol->NoMasterSimulation || pVol->Type == MasterVolume) {
            return Volume;
        }
        break;
    }

    /*
    **  Translate it back through the master volume
    **  Use 1 as the master volume if mute is set (see SetDeviceVolume)
    */

    pMaster = MasterDevice(pVol->RecordControl);

    if (!pVol->NoMasterSimulation && pMaster != NULL) {
        dwMaster = pMaster->LRVolume;

        Left = ((DWORD)LOWORD(Volume)) / AdjustMaster(LOWORD(dwMaster));
        Left <<= 8;
        if (Left > 65535) {
            Left = 65535;
        }

        Right = ((DWORD)HIWORD(Volume)) / AdjustMaster(HIWORD(dwMaster));
        Right <<= 8;
        if (Right > 65535) {
            Right = 65535;
        }
    } else {
        if (bMuted &&
            (pMaster == NULL ||
             pMaster->MuteControlId == (DWORD)-1)) {

            Left = LOWORD(Volume) >> 8;
            Right = HIWORD(Volume) >> 8;
        } else {
            Left = LOWORD(Volume);
            Right = HIWORD(Volume);
        }
    }

    pVol->LRVolume = (DWORD)MAKELONG(Left, Right);

    return pVol->LRVolume;
}

/*
**  Update the displayed 'selected' state for a line
*/

VOID UpdateSelected(PVOLUME_CONTROL pVol)
{
    if (pVol->hCheckBox != NULL) {
        BOOL bSelected = ControlSelected(pVol);
        if (pVol->Type == MasterVolume) {
            SetWindowText(pVol->hCheckBox,
                          _string(bSelected ? IDS_MUTE : IDS_UNMUTE));
        } else {
            SendMessage(pVol->hCheckBox,
                        BM_SETCHECK,
                        (WPARAM)bSelected,
                        0L);
        }
    }
}

/*
**  Update the displayed volume for a slider by getting the actual level from
**  the device and then updating the local values and informing the window
**  control(s)
*/

VOID UpdateVolume(PVOLUME_CONTROL pVol)
{
    UINT    oldVolume, oldBalance;
    DWORD   dwVolumes;
    UINT    max, min, left, right, temp;

    oldVolume = pVol->Volume;
    oldBalance = pVol->Balance;

    dwVolumes = GetDeviceVolume(pVol);

    /* figure out pan information */
    right = HIWORD(dwVolumes);
    left = LOWORD(dwVolumes);
    max = (right > left) ? right : left;
    min = (right > left) ? left : right;

    if (max == 0) {
        /* special case since then there's no panning. Therefore
            we dont know what the panning level is, therefore
            dont change the slider balance */
        pVol->Volume = 0;
        pVol->Balance = oldBalance;       /* centered */
    } else {
        pVol->Volume = max >> 8;
        temp = (UINT) (((DWORD) (max - min) << 7) / max);
        if (temp > 0x7f) temp = 0x7f;

        if (right > left)
            pVol->Balance = 0x80 + temp;
        else
            pVol->Balance = 0x7f - temp;
    }

    /* change the slider if necessary */
    if (oldVolume != pVol->Volume && pVol->hChildWnd && IsWindow(pVol->hChildWnd)) {
        SendMessage(pVol->hChildWnd,SL_PM_SETKNOBPOS,
            pVol->Volume, 0);
    }
    if (oldBalance != pVol->Balance && IsWindow(pVol->hMeterWnd)) {
        SendMessage(pVol->hMeterWnd,MB_PM_SETKNOBPOS,
            pVol->Balance, 0);
    }
}

/*
 *  Extract pertinent information for a given device type
 *  If there is an equivalent mixer device don't bother.
 */
BOOL ExtractInfo(UINT id,
                 VOLUME_DEVICE_TYPE Type,
                 LPBOOL VolSupport,
                 LPBOOL StereoSupport,
                 LPTSTR lpName,
                 PUINT Technology)
{
    UINT MixerId;

    switch (Type) {
    case MasterVolume:
        break;
    case AuxVolume:
        if (mixerGetID((HMIXEROBJ)id, &MixerId, MIXER_OBJECTF_AUX) == MMSYSERR_NOERROR) {
            return FALSE;
        }  else {
           AUXCAPS ac;
           if (auxGetDevCaps(id, &ac, sizeof(ac)) != MMSYSERR_NOERROR) {
              return FALSE;
           }
           *VolSupport = (ac.dwSupport & AUXCAPS_VOLUME) != 0;
           *StereoSupport = (ac.dwSupport & AUXCAPS_LRVOLUME) != 0;
           lstrcpyn(lpName, ac.szPname, MAXPNAMELEN);
           *Technology =
               ac.wTechnology == AUXCAPS_CDAUDIO ? VolumeTypeCD :
               ac.wTechnology == AUXCAPS_AUXIN ? VolumeTypeLineIn :
                  VolumeTypeAux;
        }
        break;
    case MidiOutVolume:
       if (mixerGetID((HMIXEROBJ)id, &MixerId, MIXER_OBJECTF_MIDIOUT) == MMSYSERR_NOERROR) {
           return FALSE;
       }  else {
          MIDIOUTCAPS mc;
          if (midiOutGetDevCaps(id, &mc, sizeof(mc)) != MMSYSERR_NOERROR) {
             return FALSE;
          }
          *VolSupport = (mc.dwSupport & MIDICAPS_VOLUME) != 0;
          *StereoSupport = (mc.dwSupport & MIDICAPS_LRVOLUME) != 0;
          lstrcpyn(lpName, mc.szPname, MAXPNAMELEN);
          *Technology =
              mc.wTechnology == MOD_SYNTH || mc.wTechnology == MOD_SQSYNTH ||
              mc.wTechnology == MOD_FMSYNTH ? VolumeTypeSynth :
              VolumeTypeMidi;
       }
       break;
    case WaveOutVolume:
        if (mixerGetID((HMIXEROBJ)id, &MixerId, MIXER_OBJECTF_WAVEOUT) == MMSYSERR_NOERROR) {
            return FALSE;
        }  else {
           WAVEOUTCAPS wc;
           if (waveOutGetDevCaps(id, &wc, sizeof(wc)) != MMSYSERR_NOERROR) {
              return FALSE;
           }
           *VolSupport = (wc.dwSupport & WAVECAPS_VOLUME) != 0;
           *StereoSupport = (wc.dwSupport & WAVECAPS_LRVOLUME) != 0;
           lstrcpyn(lpName, wc.szPname, MAXPNAMELEN);
           *Technology = VolumeTypeWave;
        }
        break;
    }

    return TRUE;
}

/*
**  NonMixerDevices
**
**  Search to see if there is a non-mixer device which is not
**  duplicated by a mixer device
**
**  If there is one return TRUE, otherwise FALSE
*/

BOOL NonMixerDevices()
{
    VOLUME_DEVICE_TYPE DeviceType;

    for (DeviceType = WaveOutVolume;
         DeviceType < NumberOfDeviceTypes;
         DeviceType++) {
        UINT DeviceId;
        UINT N;

        N = DeviceType == AuxVolume          ? auxGetNumDevs() :
            DeviceType == MidiOutVolume      ? midiOutGetNumDevs() :
                                               waveOutGetNumDevs();

        for (DeviceId = 0; DeviceId < N; DeviceId++) {
            BOOL VolumeSupport;
            BOOL StereoSupport;
            TCHAR Pname[MAXPNAMELEN];
            UINT Technology;

            if (ExtractInfo(DeviceId,
                            DeviceType,
                            &VolumeSupport,
                            &StereoSupport,
                            Pname,
                            &Technology) &&
                VolumeSupport) {
                return TRUE;
            }
        }
    }

    return FALSE;
}

/*
**  Returns an allocated array of the controls for a given line
**  Caller must LocalFree it.
*/

PMIXERCONTROL GetMixerLineControls(HMIXEROBJ MixerId,
                                   DWORD dwLineID,
                                   DWORD cControls)
{
    MIXERLINECONTROLS MixerLineControls;

    MixerLineControls.cbStruct = sizeof(MixerLineControls);
    MixerLineControls.cControls = cControls;
    MixerLineControls.dwLineID  = dwLineID;
    MixerLineControls.cbmxctrl  = sizeof(MIXERCONTROL);

    MixerLineControls.pamxctrl =
        (LPMIXERCONTROL)LocalAlloc(LPTR, cControls * sizeof(MIXERCONTROL));

    if (MixerLineControls.pamxctrl == NULL) {
        //
        // Ulp!
        //

        return NULL;
    }

    if (mixerGetLineControls(MixerId,
                             &MixerLineControls,
                             MIXER_GETLINECONTROLSF_ALL) != MMSYSERR_NOERROR) {
        LocalFree((HLOCAL)MixerLineControls.pamxctrl);
        return NULL;
    }

    return MixerLineControls.pamxctrl;
}

BOOL GetControlByType(
    HMIXEROBJ MixerId,
    DWORD dwLineId,
    DWORD dwControlType,
    PMIXERCONTROL MixerControl
)
{
    MIXERLINECONTROLS MixerLineControls;

    MixerLineControls.cbStruct      = sizeof(MixerLineControls);
    MixerLineControls.cControls     = 1;
    MixerLineControls.dwLineID      = dwLineId;
    MixerLineControls.dwControlType = dwControlType;
    MixerLineControls.cbmxctrl      = sizeof(MIXERCONTROL);

    MixerLineControls.pamxctrl = MixerControl;

    if (mixerGetLineControls(MixerId,
                             &MixerLineControls,
                             MIXER_GETLINECONTROLSF_ONEBYTYPE) != MMSYSERR_NOERROR) {
        return FALSE;
    }

    return TRUE;
}

/*
**  See if a given volume control is selected through its mux/mixer
**  Note that this state can change every time the relevant mux/mixer
**  control changes
*/
BOOL ControlSelected(
    PVOLUME_CONTROL pVol
)
{
    MIXERCONTROLDETAILS mxd;
    BOOL                bResult;

    if (pVol->Type != MixerControlVolume ||
        pVol->MuxSelectIndex == (DWORD)-1) {
        bResult = TRUE;
    } else {

        mxd.cbStruct        = sizeof(mxd);
        mxd.dwControlID     = pVol->MuxControlId;
        mxd.cChannels       = 1;
        mxd.cMultipleItems  = pVol->MuxItems;
        mxd.cbDetails       = sizeof(DWORD);
        mxd.paDetails       =
            (LPVOID)LocalAlloc(LPTR, mxd.cbDetails * mxd.cMultipleItems);

        if (mxd.paDetails == NULL) {
            return FALSE;
        }

        mixerGetControlDetails(pVol->MixerId, &mxd, MIXER_GETCONTROLDETAILSF_VALUE);
        bResult = ((LPDWORD)mxd.paDetails)[pVol->MuxSelectIndex] != 0;
        LocalFree((HLOCAL)mxd.paDetails);
    }


    if (pVol->MuteControlId != (DWORD)-1) {
        bResult = bResult && !GetMixerMute(pVol);
    }

    return bResult;
}

/*
**  The user wants this device to do its thing
*/

VOID SelectControl(
    PVOLUME_CONTROL pVol,
    BOOL            Select
)
{
    MIXERCONTROLDETAILS mxd;

    if (pVol->Type != MixerControlVolume ||
        pVol->MuxSelectIndex == (DWORD)-1 &&
        pVol->MuteControlId == (DWORD)-1) {
        return;
    }

    if (pVol->MuxSelectIndex == (DWORD)-1) {
        SetMixerMute(pVol, !Select);
    } else {
        mxd.cbStruct        = sizeof(mxd);
        mxd.dwControlID     = pVol->MuxControlId;
        mxd.cChannels       = 1;
        mxd.cMultipleItems  = pVol->MuxItems;
        mxd.cbDetails       = sizeof(DWORD);
        mxd.paDetails       =
            (LPVOID)LocalAlloc(LPTR, mxd.cbDetails * mxd.cMultipleItems);

        if (mxd.paDetails == NULL) {
            return;
        }

        if (pVol->MuxOrMixer) {
            /*
            **  Mux
            */

            ZeroMemory(mxd.paDetails, sizeof(DWORD) * mxd.cMultipleItems);
        } else {
            /*
            **  Mixer
            */

            mixerGetControlDetails(pVol->MixerId, &mxd, MIXER_GETCONTROLDETAILSF_VALUE);
        }

        ((LPDWORD)mxd.paDetails)[pVol->MuxSelectIndex] = (DWORD)Select;
        mixerSetControlDetails(pVol->MixerId, &mxd, MIXER_SETCONTROLDETAILSF_VALUE);

        /*
        **  If we have both mute and mux then turn off the mute if we
        **  activate this device
        */

        if (Select && pVol->MuteControlId != (DWORD)-1) {
            SetMixerMute(pVol, FALSE);
        }

        LocalFree((HLOCAL)mxd.paDetails);
    }
}

BOOL GetMixerMute(PVOLUME_CONTROL pVol)
{
    MIXERCONTROLDETAILS mxd;
    DWORD               dwMute;

    if (pVol->MuteControlId == (DWORD)-1) {
        return FALSE;
    }

    mxd.cbStruct        = sizeof(mxd);
    mxd.dwControlID     = pVol->MuteControlId;
    mxd.cChannels       = 1;
    mxd.cMultipleItems  = 0;
    mxd.cbDetails       = sizeof(DWORD);
    mxd.paDetails       = (LPDWORD)&dwMute;

    mixerGetControlDetails(pVol->MixerId, &mxd, MIXER_GETCONTROLDETAILSF_VALUE);

    if (pVol->Type == MasterVolume) {
        bMuted = (BOOL)dwMute;
    }
    return (BOOL)dwMute;
}
VOID SetMixerMute(PVOLUME_CONTROL pVol, BOOL Set)
{
    MIXERCONTROLDETAILS mxd;

    if (pVol->MuteControlId == (DWORD)-1) {
        return;
    }

    mxd.cbStruct        = sizeof(mxd);
    mxd.dwControlID     = pVol->MuteControlId;
    mxd.cChannels       = 1;
    mxd.cMultipleItems  = 0;
    mxd.cbDetails       = sizeof(DWORD);
    mxd.paDetails       = (LPDWORD)&Set;

    mixerSetControlDetails(pVol->MixerId, &mxd, MIXER_SETCONTROLDETAILSF_VALUE);
}

/*
**  Add a master control
**
**  Paramters
**      MixerId   - The mixer id
**      dwMaster  - The control id for volume setting
**      dwMute    - The control id for muting
**      Record    - whether it's a record or play master
*/

VOID
AddMasterControl(
    HMIXEROBJ      MixerId,
    LPMIXERLINE    LineInfo,
    LPMIXERCONTROL ControlInfo,
    DWORD          dwMute,
    BOOL           Record
)
{
    PVOLUME_CONTROL pVol;

    pVol = AddNewControl();

    if (pVol == NULL) {
        return;
    }

    pVol->Type             = MasterVolume;
    pVol->MixerId          = MixerId;
    pVol->VolumeType       = VolumeTypeMixerControl;
    pVol->Stereo           = LineInfo->cChannels > 1;
    pVol->ControlId        = ControlInfo->dwControlID;
    pVol->RecordControl    = Record;
    pVol->MuteControlId    = dwMute;
    pVol->DestLineId       = LineInfo->dwLineID;
    lstrcpy(pVol->Name, LineInfo->szShortName);

    if (FirstMasterIndex == (DWORD)-1) {
        FirstMasterIndex = pVol->Index;
    }

    if (pVol->MuteControlId != (DWORD)-1) {
        bMuted = GetMixerMute(pVol);
    }
}

VOID
AddVolumeControl(
    HMIXEROBJ      MixerId,
    BOOL           NoMasterSimulation,
    LPMIXERLINE    LineInfo,
    LPMIXERCONTROL ControlInfo,
    BOOL           Record,
    LPMIXERCONTROL MuxControl,
    DWORD          MuxSelectIndex,
    BOOL           MuxOrMixer,
    DWORD          MuteControlId,
    DWORD          DestLineId
)
{
    PVOLUME_CONTROL pVol;

    pVol = AddNewControl();

    if (pVol == NULL) {
        return;
    }

    pVol->Type             = MixerControlVolume;
    pVol->MixerId          = MixerId;
    pVol->VolumeType       = VolumeTypeMixerControl;
    pVol->Stereo           = LineInfo->cChannels > 1;
#ifndef SHOWMUX
    pVol->ControlId        = ControlInfo->dwControlID;
#else
    if (ControlInfo != NULL)
        pVol->ControlId    = ControlInfo->dwControlID;
    else
        pVol->ControlId    = (DWORD)-1;
#endif
    pVol->RecordControl    = Record;
    pVol->DestLineId       = DestLineId;

    if (Record) {
        bRecordControllable = TRUE;
    }

    pVol->NoMasterSimulation = NoMasterSimulation;
    pVol->MuxSelectIndex   = MuxSelectIndex;
    pVol->MuteControlId    = MuteControlId;
    if (MuxSelectIndex != (DWORD)-1) {
        pVol->MuxControlId     = MuxControl->dwControlID;
        pVol->MuxOrMixer       = MuxControl->dwControlType ==
                                             MIXERCONTROL_CONTROLTYPE_MUX;

        pVol->MuxItems         = MuxControl->cMultipleItems;
    }

    lstrcpy(pVol->Name, LineInfo->szShortName);
}

//
// Get the mixer stuff we're interested in
//

VOID GetMixerControls(HMIXEROBJ MixerId)
{

    MIXERCAPS       MixerCaps;
    DWORD           DestLineIndex;

    //
    //  Find the number of dest lines
    //

    if (mixerGetDevCaps((UINT)MixerId, &MixerCaps, sizeof(MixerCaps)) !=
        MMSYSERR_NOERROR) {
        return;
    }

    /*
    **  For each destination :
    **     If it's an output
    **        Find the master and mute controls if there are any
    **        Scan the source lines for suitable devices
    **
    **  NB should this just be for speakers?
    */

    for (DestLineIndex = 0;
         DestLineIndex < MixerCaps.cDestinations;
         DestLineIndex++) {

         MIXERLINE    DestLineInfo;
         MIXERCONTROL MasterVolumeControl, MasterMuteControl;
         MIXERCONTROL MuxControl;
         DWORD        dwMute;
         DWORD        dwMaster;
         BOOL         MasterFound;
         BOOL         IncludeLine;
         BOOL         RecordDestination;
         BOOL         MuxValid;
         DWORD        SourceIndex;

         MasterFound = FALSE;
         dwMute = (DWORD)-1;
         dwMaster = (DWORD)-1;

         DestLineInfo.cbStruct = sizeof(DestLineInfo);
         DestLineInfo.dwDestination = DestLineIndex;

         if (mixerGetLineInfo(MixerId,
                              &DestLineInfo,
                              MIXER_GETLINEINFOF_DESTINATION) !=
             MMSYSERR_NOERROR) {
             return;              // Bad mixer or something
         }

         if (DestLineInfo.fdwLine & MIXERLINE_LINEF_DISCONNECTED) {
             continue;
         }

         switch (DestLineInfo.dwComponentType) {

             case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
             case MIXERLINE_COMPONENTTYPE_DST_HEADPHONES:
                 RecordDestination = FALSE;
                 IncludeLine = TRUE;
                 break;

             case MIXERLINE_COMPONENTTYPE_DST_WAVEIN:
                 RecordDestination = TRUE;
                 IncludeLine = TRUE;
                 break;

             default:
                 IncludeLine = FALSE;
                 break;
         }

         if (!IncludeLine) {
             continue;
         }

         if (GetControlByType(MixerId,
                              DestLineInfo.dwLineID,
                              MIXERCONTROL_CONTROLTYPE_MUX,
                              &MuxControl) ||
             GetControlByType(MixerId,
                              DestLineInfo.dwLineID,
                              MIXERCONTROL_CONTROLTYPE_MIXER,
                              &MuxControl)) {
             /*
             **  Found a mux for this destination.
             */

             MuxValid = TRUE;
         } else {

             /*
             **  No Mux
             */

             MuxValid = FALSE;
         }

         /*
         **  Master and mute for all dest types
         */

         if (GetControlByType(MixerId,
                              DestLineInfo.dwLineID,
                              MIXERCONTROL_CONTROLTYPE_VOLUME,
                              &MasterVolumeControl)) {

             MasterFound = TRUE;
             dwMaster    = MasterVolumeControl.dwControlID;

             if (GetControlByType(MixerId,
                                  DestLineInfo.dwLineID,
                                  MIXERCONTROL_CONTROLTYPE_MUTE,
                                  &MasterMuteControl)) {
                 dwMute = MasterMuteControl.dwControlID;
             }

             /*
             **  Add master information
             */

             AddMasterControl(MixerId,
                              &DestLineInfo,
                              &MasterVolumeControl,
                              dwMute,
                              RecordDestination);

         }

         /*
         **  Now find each individual source control we want to
         **  control
         */

         for (SourceIndex = 0;
              SourceIndex < DestLineInfo.cConnections;
              SourceIndex++) {
             MIXERLINE         SourceLineInfo;
             MIXERCONTROL      SourceLineVolumeControl;
             LPMIXERCONTROL    lpSLVC = &SourceLineVolumeControl;

             BOOL              IncludeLine;
             DWORD             MuxSelectIndex;
             DWORD             MuteControlId;

             MuxSelectIndex = (DWORD)-1;

             SourceLineInfo.cbStruct      = sizeof(SourceLineInfo);
             SourceLineInfo.dwDestination = DestLineIndex;
             SourceLineInfo.dwSource      = SourceIndex;

             if (mixerGetLineInfo(MixerId,
                                  &SourceLineInfo,
                                  MIXER_GETLINEINFOF_SOURCE) !=
                 MMSYSERR_NOERROR) {
                 return;
             }

             if (SourceLineInfo.fdwLine & MIXERLINE_LINEF_DISCONNECTED) {
                 continue;
             }


             switch (SourceLineInfo.dwComponentType) {

                 /*
                 **  Only allow things we understand (and remove things
                 **  like pc speaker to keep the number of sliders down).
                 */

                 case MIXERLINE_COMPONENTTYPE_SRC_LINE:
                 case MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE:
                 case MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER:
                 case MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC:
                 case MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT:
                 case MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY:
                 case MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE:
                 case MIXERLINE_COMPONENTTYPE_SRC_DIGITAL:
                     IncludeLine = TRUE;
                     break;

                 default:
                     IncludeLine = TRUE;
                     break;
             }

             if (!IncludeLine) {
                 continue;
             }

             /*
             **  Try to get the relevant volume control
             */

             if (!GetControlByType(MixerId,
                                   SourceLineInfo.dwLineID,
                                   MIXERCONTROL_CONTROLTYPE_VOLUME,
                                   &SourceLineVolumeControl)) {
#ifdef SHOWMUX
                 lpSLVC = NULL;
#else
                 continue;
#endif
             }

             /*
             **  See if there's a mute
             */
             {
                 MIXERCONTROL MuteControl;

                 if (GetControlByType(MixerId,
                                      SourceLineInfo.dwLineID,
                                      MIXERCONTROL_CONTROLTYPE_MUTE,
                                      &MuteControl)) {
                     MuteControlId = MuteControl.dwControlID;
                 } else {
                     MuteControlId = (DWORD)-1;
                 }
             }

             /*
             ** See if we need an id to switch the recording on or
             ** off
             */

             if (MuxValid) {
                 LPMIXERCONTROLDETAILS_LISTTEXT ListText;

                 ListText = (LPMIXERCONTROLDETAILS_LISTTEXT)
                            LocalAlloc(LPTR,
                                       sizeof(*ListText) *
                                           MuxControl.cMultipleItems);

                 if (ListText != NULL) {
                     MIXERCONTROLDETAILS mxd;

                     mxd.cbStruct       = sizeof(mxd);
                     mxd.dwControlID    = MuxControl.dwControlID;
                     mxd.cChannels      = 1;   // Why the ???
                     mxd.cMultipleItems = MuxControl.cMultipleItems;
                     mxd.cbDetails      = sizeof(*ListText);
                     mxd.paDetails      = (LPVOID)ListText;

                     if (mixerGetControlDetails(
                             MixerId,
                             &mxd,
                             MIXER_GETCONTROLDETAILSF_LISTTEXT) ==
                         MMSYSERR_NOERROR) {
                         UINT i;

                         /*
                         **  Look for our line
                         */

                         for (i = 0; i < MuxControl.cMultipleItems; i++) {
                             if (ListText[i].dwParam1 ==
                                 SourceLineInfo.dwLineID) {
                                 MuxSelectIndex = i;
                             }
                         }
                     }

                     LocalFree((HLOCAL)ListText);
                 }
             }

             /*
             **  Add this volume control to the list
             */

             AddVolumeControl(MixerId,
                              MasterFound || RecordDestination,
                              &SourceLineInfo,
//                              &SourceLineVolumeControl,
                              lpSLVC,
                              RecordDestination,
                              MuxValid ? &MuxControl : NULL,
                              MuxSelectIndex,
                              MuxValid ? FALSE :
                                         MuxControl.dwControlType ==
                                         MIXERCONTROL_CONTROLTYPE_MUX,
                              MuteControlId,
                              DestLineInfo.dwLineID);
         }
    }
}

//
// Scan through all relevant devices.
// If pVol is 0 just count them, otherwise save away info
// about them as well
//

VOID FindDevices(VOLUME_DEVICE_TYPE Type)
{
   UINT N;
   UINT id;

   N = Type == MasterVolume       ? 0 :
       Type == AuxVolume          ? auxGetNumDevs() :
       Type == MidiOutVolume      ? midiOutGetNumDevs() :
       Type == WaveOutVolume      ? waveOutGetNumDevs() :
       Type == MixerControlVolume ? mixerGetNumDevs() :
       0;


   for (id = 0; id < N; id++) {
      if (Type == MixerControlVolume) {
          //
          //  Find out how many suitable volume controls this mixer
          //  supports.
          //
          //  This is incredibly laborious because we can't just enumerate
          //  the controls (!).
          //
          //  This next call has the side effect of generating the mixer
          //  master stuff too and a set of mixer handles.
          //

          GetMixerControls(MixerId);
          return;
      } else {
          BOOL Volume;
          BOOL Stereo;
          TCHAR Name[MAXPNAMELEN];
          UINT Technology;

          if (ExtractInfo(id, Type, &Volume, &Stereo, Name, &Technology)) {
              if (Volume) {
                 PVOLUME_CONTROL pVol;

                 /*
                 **  Supports volume setting
                 */

                 pVol = AddNewControl();

                 if (pVol) {
                     pVol->id = id;
                     pVol->Type = Type;
                     pVol->VolumeType = Technology;
                     pVol->Stereo = Stereo;
                     pVol++;
                 }
              }
          } else {
             continue; // Don't use this one
          }
      }
   }
}

/*
 *  Create and initialize our volume array
 *
 *  On exit
 *    NumberOfDevices is set to the number of devices we want
 *    Vol is an array of size NumberOfDevices (may be 0)
 */

BOOL VolInit(VOID)
{
    int i;
    WORD wLeft, wRight, wMax, wMin, wTemp;

    /*
    **  Free any volume stuff currently present
    */

    if (Vol) {
        HGLOBAL hVol;
        int     i;

        /*
        **  Free all the windows
        */
        for (i = 0; i < NumberOfDevices; i++) {
            DestroyOurWindow(&Vol[i].hChildWnd);
            DestroyOurWindow(&Vol[i].hMeterWnd);
            DestroyOurWindow(&Vol[i].hStatic);
            DestroyOurWindow(&Vol[i].hCheckBox);
        }

        /*
        **  Free the memory
        */

        hVol = GlobalHandle(Vol);
        GlobalUnlock(hVol);
        GlobalFree(hVol);
        Vol = NULL;

        /*
        **  Initialize globals
        */

        bRecordControllable = FALSE;
    }

    /*
    **  No master volume controls found yet
    */

    FirstMasterIndex = (DWORD)-1;

    /*
     *  Scan all the device types we're interested in :
     *     wave out
     *     midi out
     *     aux
     */

     if ((DWORD)MixerId != (DWORD)-1) {
         FindDevices(MixerControlVolume);
     } else {
         for (i = WaveOutVolume; i < NumberOfDeviceTypes; i++) {
             FindDevices(i);
         }
     }

     if (NumberOfDevices == 0) {
         return FALSE;
     }

     if (FirstMasterIndex == (DWORD)-1) {
         PVOLUME_CONTROL pMaster;
         BOOL            bStereo;

         /*
         **  Find if any devices are stereo
         */

         bStereo = FALSE;

         for (i = 0; i < NumberOfDevices; i++) {
             if (Vol[i].Stereo) {
                 bStereo = TRUE;
                 break;
             }
         }

         /*
         **  Create a default volume control
         */

         pMaster = AddNewControl();
         if (pMaster == NULL) {
             return FALSE;
         }

         pMaster->Type       = MasterVolume;
         pMaster->VolumeType = -1;

         pMaster->Stereo     = bStereo;

         FirstMasterIndex = pMaster->Index;

         wLeft = (WORD)MasterLeft;
         wRight = (WORD)MasterRight;

         pMaster->LRVolume = MAKELONG(wLeft, wRight);

         if (wRight > wLeft) {
             wMax = wRight;
             wMin = wLeft;
         } else {
             wMax = wLeft;
             wMin = wRight;
         }

         if (wMax == 0) {

            pMaster->Volume = 0;
            pMaster->Balance = 0x80;       /* centered */

         } else {

            pMaster->Volume = wMax >> 8;

            wTemp = (UINT) (((DWORD) (wMax - wMin) << 7) / wMax);
            if (wTemp > 0x7f) wTemp = 0x7f;

            if (wRight > wLeft)
                pMaster->Balance = 0x80 + wTemp;
            else
                pMaster->Balance = 0x7f - wTemp;
         }

     }

     return TRUE;
}

/*
**  Called when a mixer calls us back with a control change
*/

VOID ControlChange(HMIXER hMixer, DWORD ControlId)
{
    UINT        i;
    HMIXEROBJ   MixerId;
    MMRESULT    mmr;

    mmr = mixerGetID((HMIXEROBJ)hMixer, (PUINT)&MixerId, MIXER_OBJECTF_HMIXER);

    if (mmr != MMSYSERR_NOERROR) {
        return;
    }
    for (i = 0; i < (UINT)NumberOfDevices; i++) {

        if (Vol[i].MixerId == MixerId) {
            if (Vol[i].VolumeType == VolumeTypeMixerControl) {
                if (ControlId == Vol[i].ControlId) {
                    UpdateVolume(&Vol[i]);

                    /*
                    **  Volume controls only affect one control
                    **  (unlike muxes)
                    */

                    break;
                } else {
                    if (ControlId == Vol[i].MuxControlId ||
                        ControlId == Vol[i].MuteControlId) {

                        UpdateSelected(&Vol[i]);
                    }
                }
            }
        } /* MixerId == Vol[i].MixerId */
    }
}

PVOLUME_CONTROL FirstDevice(BOOL bRecord)
{
    UINT i;
    for (i = 0; i < (UINT)NumberOfDevices; i++) {
        if (Vol[i].Type          != MasterVolume &&
            Vol[i].RecordControl == bRecord) {
            return &Vol[i];
        }
    }

    return NULL;
}
PVOLUME_CONTROL LastDevice(BOOL bRecord)
{
    UINT i;
    for (i = NumberOfDevices; i > 0; i--) {
        if (Vol[i - 1].Type          != MasterVolume &&
            Vol[i - 1].RecordControl == bRecord) {
            return &Vol[i - 1];
        }
    }

    return NULL;
}

PVOLUME_CONTROL NextDevice(PVOLUME_CONTROL pVol)
{
    UINT            i;

    for (i = pVol->Index == (UINT)NumberOfDevices - 1 ? 0 : pVol->Index + 1 ;
         i != pVol->Index;
         i = i == (UINT)NumberOfDevices - 1 ? 0 : i + 1) {

        if (Vol[i].Type != MasterVolume &&
            Vol[i].RecordControl == pVol->RecordControl) {
           break;
        }
    }

    return &Vol[i];
}

PVOLUME_CONTROL NextDeviceNoWrap(PVOLUME_CONTROL pVol)
{
    UINT            i;

    for (i = pVol->Index + 1 ;
         i < (UINT)NumberOfDevices;
         i = i + 1) {

        if (Vol[i].Type != MasterVolume &&
            Vol[i].RecordControl == pVol->RecordControl) {
           return &Vol[i];
        }
    }

    return NULL;
}
PVOLUME_CONTROL PrevDevice(PVOLUME_CONTROL pVol)
{
    UINT            i;

    for (i = pVol->Index == 0 ? NumberOfDevices - 1 : pVol->Index - 1;
         i != pVol->Index;
         i = i == 0 ? NumberOfDevices - 1 : i - 1) {

        if (Vol[i].Type != MasterVolume &&
            Vol[i].RecordControl == pVol->RecordControl) {
           return &Vol[i];
        }
    }

    return &Vol[i];
}

PVOLUME_CONTROL PrevDeviceNoWrap(PVOLUME_CONTROL pVol)
{
    UINT            i;

    for (i = pVol->Index;
         i != 0;
         i = i - 1) {

        if (Vol[i - 1].Type != MasterVolume &&
            Vol[i - 1].RecordControl == pVol->RecordControl) {
           return &Vol[i - 1];
        }
    }

    return NULL;
}

PVOLUME_CONTROL MasterDevice(BOOL bRecord)
{
    UINT i;

    for (i = 0 ; i < (UINT)NumberOfDevices; i++) {
        if (Vol[i].Type == MasterVolume &&
            Vol[i].RecordControl == bRecord) {
            return &Vol[i];
        }
    }

    return NULL;
}