/*
 * Test mixer
 *
 * Copyright (c) 2004 Robert Reif
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

/*
 * To Do:
 * add interactive tests
 */

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "wine/test.h"
#include "windef.h"
#include "winbase.h"
#include "winnls.h"
#include "mmsystem.h"

#include "winmm_test.h"

static BOOL compare_uint(unsigned int x, unsigned int y, unsigned int max_diff)
{
    unsigned int diff = x > y ? x - y : y - x;
    return diff <= max_diff;
}

static const char * line_flags(DWORD fdwLine)
{
    static char flags[100];
    BOOL first=TRUE;
    flags[0]=0;
    if (fdwLine&MIXERLINE_LINEF_ACTIVE) {
        strcat(flags,"MIXERLINE_LINEF_ACTIVE");
        first=FALSE;
    }
    if (fdwLine&MIXERLINE_LINEF_DISCONNECTED) {
        if (!first)
            strcat(flags, "|");

        strcat(flags,"MIXERLINE_LINEF_DISCONNECTED");
        first=FALSE;
    }

    if (fdwLine&MIXERLINE_LINEF_SOURCE) {
        if (!first)
            strcat(flags, "|");

        strcat(flags,"MIXERLINE_LINEF_SOURCE");
    }

    return flags;
}

static const char * component_type(DWORD dwComponentType)
{
#define TYPE_TO_STR(x) case x: return #x
    switch (dwComponentType) {
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_UNDEFINED);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_DIGITAL);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_LINE);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_MONITOR);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_SPEAKERS);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_HEADPHONES);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_TELEPHONE);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_WAVEIN);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_VOICEIN);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_DIGITAL);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_LINE);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY);
    TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_ANALOG);
    }
#undef TYPE_TO_STR
    return "UNKNOWN";
}

static const char * target_type(DWORD dwType)
{
#define TYPE_TO_STR(x) case x: return #x
    switch (dwType) {
    TYPE_TO_STR(MIXERLINE_TARGETTYPE_UNDEFINED);
    TYPE_TO_STR(MIXERLINE_TARGETTYPE_WAVEOUT);
    TYPE_TO_STR(MIXERLINE_TARGETTYPE_WAVEIN);
    TYPE_TO_STR(MIXERLINE_TARGETTYPE_MIDIOUT);
    TYPE_TO_STR(MIXERLINE_TARGETTYPE_MIDIIN);
    TYPE_TO_STR(MIXERLINE_TARGETTYPE_AUX);
    }
#undef TYPE_TO_STR
    return "UNKNOWN";
}

static const char * control_type(DWORD dwControlType)
{
#define TYPE_TO_STR(x) case x: return #x
    switch (dwControlType) {
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME);
    TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME);
    }
#undef TYPE_TO_STR
    return "UNKNOWN";
}

static const char * control_flags(DWORD fdwControl)
{
    static char flags[100];
    BOOL first=TRUE;
    flags[0]=0;
    if (fdwControl&MIXERCONTROL_CONTROLF_UNIFORM) {
        strcat(flags,"MIXERCONTROL_CONTROLF_UNIFORM");
        first=FALSE;
    }
    if (fdwControl&MIXERCONTROL_CONTROLF_MULTIPLE) {
        if (!first)
            strcat(flags, "|");

        strcat(flags,"MIXERCONTROL_CONTROLF_MULTIPLE");
        first=FALSE;
    }

    if (fdwControl&MIXERCONTROL_CONTROLF_DISABLED) {
        if (!first)
            strcat(flags, "|");

        strcat(flags,"MIXERCONTROL_CONTROLF_DISABLED");
    }

    return flags;
}

static void test_mixerClose(HMIXER mix)
{
    MMRESULT rc;

    rc = mixerClose(mix);
    ok(rc == MMSYSERR_NOERROR || rc == MMSYSERR_INVALHANDLE,
       "mixerClose: MMSYSERR_NOERROR or MMSYSERR_INVALHANDLE expected, got %s\n",
       mmsys_error(rc));
}

static void mixer_test_controlA(HMIXEROBJ mix, MIXERCONTROLA *control)
{
    MMRESULT rc;

    if ((control->dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME) ||
        (control->dwControlType == MIXERCONTROL_CONTROLTYPE_UNSIGNED)) {
        MIXERCONTROLDETAILS details;
        MIXERCONTROLDETAILS_UNSIGNED value;

        details.cbStruct = sizeof(MIXERCONTROLDETAILS);
        details.dwControlID = control->dwControlID;
        details.cChannels = 1;
        U(details).cMultipleItems = 0;
        details.cbDetails = sizeof(value);

        /* test NULL paDetails */
        details.paDetails = NULL;
        rc = mixerGetControlDetailsA(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE);
        ok(rc==MMSYSERR_INVALPARAM,
           "mixerGetDevCapsA: MMSYSERR_INVALPARAM expected, got %s\n",
           mmsys_error(rc));

        /* read the current control value */
        details.paDetails = &value;
        rc = mixerGetControlDetailsA(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE);
        ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
           "MMSYSERR_NOERROR expected, got %s\n",
           mmsys_error(rc));
        if (rc==MMSYSERR_NOERROR && winetest_interactive) {
            MIXERCONTROLDETAILS new_details;
            MIXERCONTROLDETAILS_UNSIGNED new_value;

            trace("            Value=%ld\n",value.dwValue);

            if (value.dwValue + control->Metrics.cSteps < S1(control->Bounds).dwMaximum)
                new_value.dwValue = value.dwValue + control->Metrics.cSteps;
            else
                new_value.dwValue = value.dwValue - control->Metrics.cSteps;

            new_details.cbStruct = sizeof(MIXERCONTROLDETAILS);
            new_details.dwControlID = control->dwControlID;
            new_details.cChannels = 1;
            U(new_details).cMultipleItems = 0;
            new_details.paDetails = &new_value;
            new_details.cbDetails = sizeof(new_value);

            /* change the control value by one step */
            rc = mixerSetControlDetails(mix, &new_details, MIXER_SETCONTROLDETAILSF_VALUE);
            ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
               "MMSYSERR_NOERROR expected, got %s\n",
               mmsys_error(rc));
            if (rc==MMSYSERR_NOERROR) {
                MIXERCONTROLDETAILS ret_details;
                MIXERCONTROLDETAILS_UNSIGNED ret_value;

                ret_details.cbStruct = sizeof(MIXERCONTROLDETAILS);
                ret_details.dwControlID = control->dwControlID;
                ret_details.cChannels = 1;
                U(ret_details).cMultipleItems = 0;
                ret_details.paDetails = &ret_value;
                ret_details.cbDetails = sizeof(ret_value);

                /* read back the new control value */
                rc = mixerGetControlDetailsA(mix, &ret_details, MIXER_GETCONTROLDETAILSF_VALUE);
                ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
                   "MMSYSERR_NOERROR expected, got %s\n",
                   mmsys_error(rc));
                if (rc==MMSYSERR_NOERROR) {
                    /* result may not match exactly because of rounding */
                    ok(compare_uint(ret_value.dwValue, new_value.dwValue, 1),
                       "Couldn't change value from %ld to %ld, returned %ld\n",
                       value.dwValue,new_value.dwValue,ret_value.dwValue);

                    if (compare_uint(ret_value.dwValue, new_value.dwValue, 1)) {
                        details.cbStruct = sizeof(MIXERCONTROLDETAILS);
                        details.dwControlID = control->dwControlID;
                        details.cChannels = 1;
                        U(details).cMultipleItems = 0;
                        details.paDetails = &value;
                        details.cbDetails = sizeof(value);

                        /* restore original value */
                        rc = mixerSetControlDetails(mix, &details, MIXER_SETCONTROLDETAILSF_VALUE);
                        ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                           "MMSYSERR_NOERROR expected, got %s\n",
                           mmsys_error(rc));
                    }
                }
            }
        }
    } else if ((control->dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE) ||
        (control->dwControlType == MIXERCONTROL_CONTROLTYPE_BOOLEAN) ||
        (control->dwControlType == MIXERCONTROL_CONTROLTYPE_BUTTON)) {
        MIXERCONTROLDETAILS details;
        MIXERCONTROLDETAILS_BOOLEAN value;

        details.cbStruct = sizeof(MIXERCONTROLDETAILS);
        details.dwControlID = control->dwControlID;
        details.cChannels = 1;
        U(details).cMultipleItems = 0;
        details.paDetails = &value;
        details.cbDetails = sizeof(value);

        rc = mixerGetControlDetailsA(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE);
        ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
           "MMSYSERR_NOERROR expected, got %s\n",
           mmsys_error(rc));
        if (rc==MMSYSERR_NOERROR && winetest_interactive) {
            MIXERCONTROLDETAILS new_details;
            MIXERCONTROLDETAILS_BOOLEAN new_value;

            trace("            Value=%ld\n",value.fValue);

            if (value.fValue == FALSE)
                new_value.fValue = TRUE;
            else
                new_value.fValue = FALSE;

            new_details.cbStruct = sizeof(MIXERCONTROLDETAILS);
            new_details.dwControlID = control->dwControlID;
            new_details.cChannels = 1;
            U(new_details).cMultipleItems = 0;
            new_details.paDetails = &new_value;
            new_details.cbDetails = sizeof(new_value);

            /* change the control value by one step */
            rc = mixerSetControlDetails(mix, &new_details, MIXER_SETCONTROLDETAILSF_VALUE);
            ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
               "MMSYSERR_NOERROR expected, got %s\n",
               mmsys_error(rc));
            if (rc==MMSYSERR_NOERROR) {
                MIXERCONTROLDETAILS ret_details;
                MIXERCONTROLDETAILS_BOOLEAN ret_value;

                ret_details.cbStruct = sizeof(MIXERCONTROLDETAILS);
                ret_details.dwControlID = control->dwControlID;
                ret_details.cChannels = 1;
                U(ret_details).cMultipleItems = 0;
                ret_details.paDetails = &ret_value;
                ret_details.cbDetails = sizeof(ret_value);

                /* read back the new control value */
                rc = mixerGetControlDetailsA(mix, &ret_details, MIXER_GETCONTROLDETAILSF_VALUE);
                ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
                   "MMSYSERR_NOERROR expected, got %s\n",
                   mmsys_error(rc));
                if (rc==MMSYSERR_NOERROR) {
                    /* result may not match exactly because of rounding */
                    ok(ret_value.fValue==new_value.fValue,
                       "Couldn't change value from %ld to %ld, returned %ld\n",
                       value.fValue,new_value.fValue,ret_value.fValue);

                    if (ret_value.fValue==new_value.fValue) {
                        details.cbStruct = sizeof(MIXERCONTROLDETAILS);
                        details.dwControlID = control->dwControlID;
                        details.cChannels = 1;
                        U(details).cMultipleItems = 0;
                        details.paDetails = &value;
                        details.cbDetails = sizeof(value);

                        /* restore original value */
                        rc = mixerSetControlDetails(mix, &details, MIXER_SETCONTROLDETAILSF_VALUE);
                        ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                           "MMSYSERR_NOERROR expected, got %s\n",
                           mmsys_error(rc));
                    }
                }
            }
        }
    } else {
        /* FIXME */
    }
}

static void mixer_test_deviceA(int device)
{
    MIXERCAPSA capsA;
    HMIXEROBJ mix;
    MMRESULT rc;
    DWORD d,s,ns,nc;

    rc=mixerGetDevCapsA(device,0,sizeof(capsA));
    ok(rc==MMSYSERR_INVALPARAM,
       "mixerGetDevCapsA: MMSYSERR_INVALPARAM expected, got %s\n",
       mmsys_error(rc));

    rc=mixerGetDevCapsA(device,&capsA,4);
    ok(rc==MMSYSERR_NOERROR,
       "mixerGetDevCapsA: MMSYSERR_NOERROR expected, got %s\n",
       mmsys_error(rc));

    rc=mixerGetDevCapsA(device,&capsA,sizeof(capsA));
    ok(rc==MMSYSERR_NOERROR,
       "mixerGetDevCapsA: MMSYSERR_NOERROR expected, got %s\n",
       mmsys_error(rc));

    if (winetest_interactive) {
        trace("  %d: \"%s\" %d.%d (%d:%d) destinations=%ld\n", device,
              capsA.szPname, capsA.vDriverVersion >> 8,
              capsA.vDriverVersion & 0xff,capsA.wMid,capsA.wPid,
              capsA.cDestinations);
    } else {
        trace("  %d: \"%s\" %d.%d (%d:%d)\n", device,
              capsA.szPname, capsA.vDriverVersion >> 8,
              capsA.vDriverVersion & 0xff,capsA.wMid,capsA.wPid);
    }

    rc = mixerOpen((HMIXER*)&mix, device, 0, 0, 0);
    ok(rc==MMSYSERR_NOERROR,
       "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",mmsys_error(rc));
    if (rc==MMSYSERR_NOERROR) {
        MIXERCAPSA capsA2;

        rc=mixerGetDevCapsA((UINT_PTR)mix,&capsA2,sizeof(capsA2));
        ok(rc==MMSYSERR_NOERROR,
           "mixerGetDevCapsA: MMSYSERR_NOERROR expected, got %s\n",
           mmsys_error(rc));
        ok(!strcmp(capsA2.szPname, capsA.szPname), "Got wrong device caps\n");

        for (d=0;d<capsA.cDestinations;d++) {
            MIXERLINEA mixerlineA;
            mixerlineA.cbStruct = 0;
            mixerlineA.dwDestination=d;
            rc = mixerGetLineInfoA(mix, &mixerlineA, MIXER_GETLINEINFOF_DESTINATION);
            ok(rc==MMSYSERR_INVALPARAM,
               "mixerGetLineInfoA(MIXER_GETLINEINFOF_DESTINATION): "
               "MMSYSERR_INVALPARAM expected, got %s\n",
               mmsys_error(rc));

            mixerlineA.cbStruct = sizeof(mixerlineA);
            mixerlineA.dwDestination=capsA.cDestinations;
            rc = mixerGetLineInfoA(mix, &mixerlineA, MIXER_GETLINEINFOF_DESTINATION);
            ok(rc==MMSYSERR_INVALPARAM||rc==MIXERR_INVALLINE,
               "mixerGetLineInfoA(MIXER_GETLINEINFOF_DESTINATION): "
               "MMSYSERR_INVALPARAM or MIXERR_INVALLINE expected, got %s\n",
               mmsys_error(rc));

            mixerlineA.cbStruct = sizeof(mixerlineA);
            mixerlineA.dwDestination=d;
            rc = mixerGetLineInfoA(mix, 0, MIXER_GETLINEINFOF_DESTINATION);
            ok(rc==MMSYSERR_INVALPARAM,
               "mixerGetLineInfoA(MIXER_GETLINEINFOF_DESTINATION): "
               "MMSYSERR_INVALPARAM expected, got %s\n",
               mmsys_error(rc));

            mixerlineA.cbStruct = sizeof(mixerlineA);
            mixerlineA.dwDestination=d;
            rc = mixerGetLineInfoA(mix, &mixerlineA, -1);
            ok(rc==MMSYSERR_INVALFLAG,
               "mixerGetLineInfoA(-1): MMSYSERR_INVALFLAG expected, got %s\n",
               mmsys_error(rc));

            mixerlineA.cbStruct = sizeof(mixerlineA);
            mixerlineA.dwDestination=d;
            mixerlineA.dwUser = (ULONG_PTR)0xdeadbeef;
            rc = mixerGetLineInfoA(mix, &mixerlineA, MIXER_GETLINEINFOF_DESTINATION);
            ok(rc==MMSYSERR_NOERROR||rc==MMSYSERR_NODRIVER,
               "mixerGetLineInfoA(MIXER_GETLINEINFOF_DESTINATION): "
               "MMSYSERR_NOERROR expected, got %s\n",
               mmsys_error(rc));
            ok(mixerlineA.dwUser == 0, "dwUser was not reset\n");
            if (rc==MMSYSERR_NODRIVER)
                trace("  No Driver\n");
            else if (rc==MMSYSERR_NOERROR) {
	      if (winetest_interactive) {
                trace("    %ld: \"%s\" (%s) Destination=%ld Source=%ld\n",
                      d,mixerlineA.szShortName, mixerlineA.szName,
                      mixerlineA.dwDestination,mixerlineA.dwSource);
                trace("        LineID=%08lx Channels=%ld "
                      "Connections=%ld Controls=%ld\n",
                      mixerlineA.dwLineID,mixerlineA.cChannels,
                      mixerlineA.cConnections,mixerlineA.cControls);
                trace("        State=0x%08lx(%s)\n",
                      mixerlineA.fdwLine,line_flags(mixerlineA.fdwLine));
                trace("        ComponentType=%s\n",
                      component_type(mixerlineA.dwComponentType));
                trace("        Type=%s\n",
                      target_type(mixerlineA.Target.dwType));
                trace("        Device=%ld (%s) %d.%d (%d:%d)\n",
                      mixerlineA.Target.dwDeviceID,
                      mixerlineA.Target.szPname,
                      mixerlineA.Target.vDriverVersion >> 8,
                      mixerlineA.Target.vDriverVersion & 0xff,
                      mixerlineA.Target.wMid, mixerlineA.Target.wPid);
	      }
              ns=mixerlineA.cConnections;
              for(s=0;s<ns;s++) {
                mixerlineA.cbStruct = sizeof(mixerlineA);
                mixerlineA.dwDestination=d;
                mixerlineA.dwSource=s;
                rc = mixerGetLineInfoA(mix, &mixerlineA, MIXER_GETLINEINFOF_SOURCE);
                ok(rc==MMSYSERR_NOERROR||rc==MMSYSERR_NODRIVER,
                   "mixerGetLineInfoA(MIXER_GETLINEINFOF_SOURCE): "
                   "MMSYSERR_NOERROR expected, got %s\n",
                   mmsys_error(rc));
                if (rc==MMSYSERR_NODRIVER)
                    trace("  No Driver\n");
                else if (rc==MMSYSERR_NOERROR) {
                    LPMIXERCONTROLA    array;
                    MIXERLINECONTROLSA controls;
                    if (winetest_interactive) {
                        trace("      %ld: \"%s\" (%s) Destination=%ld Source=%ld\n",
                              s,mixerlineA.szShortName, mixerlineA.szName,
                              mixerlineA.dwDestination,mixerlineA.dwSource);
                        trace("          LineID=%08lx Channels=%ld "
                              "Connections=%ld Controls=%ld\n",
                              mixerlineA.dwLineID,mixerlineA.cChannels,
                              mixerlineA.cConnections,mixerlineA.cControls);
                        trace("          State=0x%08lx(%s)\n",
                              mixerlineA.fdwLine,line_flags(mixerlineA.fdwLine));
                        trace("          ComponentType=%s\n",
                              component_type(mixerlineA.dwComponentType));
                        trace("          Type=%s\n",
                              target_type(mixerlineA.Target.dwType));
                        trace("          Device=%ld (%s) %d.%d (%d:%d)\n",
                              mixerlineA.Target.dwDeviceID,
                              mixerlineA.Target.szPname,
                              mixerlineA.Target.vDriverVersion >> 8,
                              mixerlineA.Target.vDriverVersion & 0xff,
                              mixerlineA.Target.wMid, mixerlineA.Target.wPid);
                    }
                    if (mixerlineA.cControls) {
                        array=HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,
                            mixerlineA.cControls*sizeof(MIXERCONTROLA));
                        if (array) {
                            memset(&controls, 0, sizeof(controls));

                            rc = mixerGetLineControlsA(mix, 0, MIXER_GETLINECONTROLSF_ALL);
                            ok(rc==MMSYSERR_INVALPARAM,
                               "mixerGetLineControlsA(MIXER_GETLINECONTROLSF_ALL): "
                               "MMSYSERR_INVALPARAM expected, got %s\n",
                               mmsys_error(rc));

                            rc = mixerGetLineControlsA(mix, &controls, -1);
                            ok(rc==MMSYSERR_INVALFLAG||rc==MMSYSERR_INVALPARAM,
                               "mixerGetLineControlsA(-1): "
                               "MMSYSERR_INVALFLAG or MMSYSERR_INVALPARAM expected, got %s\n",
                               mmsys_error(rc));

                            controls.cbStruct = sizeof(MIXERLINECONTROLSA);
                            controls.cControls = mixerlineA.cControls;
                            controls.dwLineID = mixerlineA.dwLineID;
                            controls.pamxctrl = array;
                            controls.cbmxctrl = sizeof(MIXERCONTROLA);

                            /* FIXME: do MIXER_GETLINECONTROLSF_ONEBYID
                             * and MIXER_GETLINECONTROLSF_ONEBYTYPE
                             */
                            rc = mixerGetLineControlsA(mix, &controls, MIXER_GETLINECONTROLSF_ALL);
                            ok(rc==MMSYSERR_NOERROR,
                               "mixerGetLineControlsA(MIXER_GETLINECONTROLSF_ALL): "
                               "MMSYSERR_NOERROR expected, got %s\n",
                               mmsys_error(rc));
                            if (rc==MMSYSERR_NOERROR) {
                                for(nc=0;nc<mixerlineA.cControls;nc++) {
                                    if (winetest_interactive) {
                                        trace("        %ld: \"%s\" (%s) ControlID=%ld\n", nc,
                                              array[nc].szShortName,
                                              array[nc].szName, array[nc].dwControlID);
                                        trace("            ControlType=%s\n",
                                               control_type(array[nc].dwControlType));
                                        trace("            Control=0x%08lx(%s)\n",
                                              array[nc].fdwControl,
                                              control_flags(array[nc].fdwControl));
                                        trace("            Items=%ld Min=%ld Max=%ld Step=%ld\n",
                                              array[nc].cMultipleItems,
                                              S1(array[nc].Bounds).dwMinimum,
                                              S1(array[nc].Bounds).dwMaximum,
                                              array[nc].Metrics.cSteps);
                                    }

                                    mixer_test_controlA(mix, &array[nc]);
                                }
                            }

                            HeapFree(GetProcessHeap(),0,array);
                        }
                    }
                }
              }
            }
        }
        test_mixerClose((HMIXER)mix);
    }
}

static void mixer_test_controlW(HMIXEROBJ mix, MIXERCONTROLW *control)
{
    MMRESULT rc;

    if ((control->dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME) ||
        (control->dwControlType == MIXERCONTROL_CONTROLTYPE_UNSIGNED)) {
        MIXERCONTROLDETAILS details;
        MIXERCONTROLDETAILS_UNSIGNED value;

        details.cbStruct = sizeof(MIXERCONTROLDETAILS);
        details.dwControlID = control->dwControlID;
        details.cChannels = 1;
        U(details).cMultipleItems = 0;
        details.paDetails = &value;
        details.cbDetails = sizeof(value);

        /* read the current control value */
        rc = mixerGetControlDetailsW(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE);
        ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
           "MMSYSERR_NOERROR expected, got %s\n",
           mmsys_error(rc));
        if (rc==MMSYSERR_NOERROR && winetest_interactive) {
            MIXERCONTROLDETAILS new_details;
            MIXERCONTROLDETAILS_UNSIGNED new_value;

            trace("            Value=%ld\n",value.dwValue);

            if (value.dwValue + control->Metrics.cSteps < S1(control->Bounds).dwMaximum)
                new_value.dwValue = value.dwValue + control->Metrics.cSteps;
            else
                new_value.dwValue = value.dwValue - control->Metrics.cSteps;

            new_details.cbStruct = sizeof(MIXERCONTROLDETAILS);
            new_details.dwControlID = control->dwControlID;
            new_details.cChannels = 1;
            U(new_details).cMultipleItems = 0;
            new_details.paDetails = &new_value;
            new_details.cbDetails = sizeof(new_value);

            /* change the control value by one step */
            rc = mixerSetControlDetails(mix, &new_details, MIXER_SETCONTROLDETAILSF_VALUE);
            ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
               "MMSYSERR_NOERROR expected, got %s\n",
               mmsys_error(rc));
            if (rc==MMSYSERR_NOERROR) {
                MIXERCONTROLDETAILS ret_details;
                MIXERCONTROLDETAILS_UNSIGNED ret_value;

                ret_details.cbStruct = sizeof(MIXERCONTROLDETAILS);
                ret_details.dwControlID = control->dwControlID;
                ret_details.cChannels = 1;
                U(ret_details).cMultipleItems = 0;
                ret_details.paDetails = &ret_value;
                ret_details.cbDetails = sizeof(ret_value);

                /* read back the new control value */
                rc = mixerGetControlDetailsW(mix, &ret_details, MIXER_GETCONTROLDETAILSF_VALUE);
                ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
                   "MMSYSERR_NOERROR expected, got %s\n",
                   mmsys_error(rc));
                if (rc==MMSYSERR_NOERROR) {
                    /* result may not match exactly because of rounding */
                    ok(compare_uint(ret_value.dwValue, new_value.dwValue, 1),
                       "Couldn't change value from %ld to %ld, returned %ld\n",
                       value.dwValue,new_value.dwValue,ret_value.dwValue);

                    if (compare_uint(ret_value.dwValue, new_value.dwValue, 1)) {
                        details.cbStruct = sizeof(MIXERCONTROLDETAILS);
                        details.dwControlID = control->dwControlID;
                        details.cChannels = 1;
                        U(details).cMultipleItems = 0;
                        details.paDetails = &value;
                        details.cbDetails = sizeof(value);

                        /* restore original value */
                        rc = mixerSetControlDetails(mix, &details, MIXER_SETCONTROLDETAILSF_VALUE);
                        ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                           "MMSYSERR_NOERROR expected, got %s\n",
                           mmsys_error(rc));
                    }
                }
            }
        }
    } else if ((control->dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE) ||
        (control->dwControlType == MIXERCONTROL_CONTROLTYPE_BOOLEAN) ||
        (control->dwControlType == MIXERCONTROL_CONTROLTYPE_BUTTON)) {
        MIXERCONTROLDETAILS details;
        MIXERCONTROLDETAILS_BOOLEAN value;

        details.cbStruct = sizeof(MIXERCONTROLDETAILS);
        details.dwControlID = control->dwControlID;
        details.cChannels = 1;
        U(details).cMultipleItems = 0;
        details.paDetails = &value;
        details.cbDetails = sizeof(value);

        rc = mixerGetControlDetailsW(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE);
        ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
           "MMSYSERR_NOERROR expected, got %s\n",
           mmsys_error(rc));
        if (rc==MMSYSERR_NOERROR && winetest_interactive) {
            MIXERCONTROLDETAILS new_details;
            MIXERCONTROLDETAILS_BOOLEAN new_value;

            trace("            Value=%ld\n",value.fValue);

            if (value.fValue == FALSE)
                new_value.fValue = TRUE;
            else
                new_value.fValue = FALSE;

            new_details.cbStruct = sizeof(MIXERCONTROLDETAILS);
            new_details.dwControlID = control->dwControlID;
            new_details.cChannels = 1;
            U(new_details).cMultipleItems = 0;
            new_details.paDetails = &new_value;
            new_details.cbDetails = sizeof(new_value);

            /* change the control value by one step */
            rc = mixerSetControlDetails(mix, &new_details, MIXER_SETCONTROLDETAILSF_VALUE);
            ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
               "MMSYSERR_NOERROR expected, got %s\n",
               mmsys_error(rc));
            if (rc==MMSYSERR_NOERROR) {
                MIXERCONTROLDETAILS ret_details;
                MIXERCONTROLDETAILS_BOOLEAN ret_value;

                ret_details.cbStruct = sizeof(MIXERCONTROLDETAILS);
                ret_details.dwControlID = control->dwControlID;
                ret_details.cChannels = 1;
                U(ret_details).cMultipleItems = 0;
                ret_details.paDetails = &ret_value;
                ret_details.cbDetails = sizeof(ret_value);

                /* read back the new control value */
                rc = mixerGetControlDetailsW(mix, &ret_details, MIXER_GETCONTROLDETAILSF_VALUE);
                ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
                   "MMSYSERR_NOERROR expected, got %s\n",
                   mmsys_error(rc));
                if (rc==MMSYSERR_NOERROR) {
                    /* result may not match exactly because of rounding */
                    ok(ret_value.fValue==new_value.fValue,
                       "Couldn't change value from %ld to %ld, returned %ld\n",
                       value.fValue,new_value.fValue,ret_value.fValue);

                    if (ret_value.fValue==new_value.fValue) {
                        details.cbStruct = sizeof(MIXERCONTROLDETAILS);
                        details.dwControlID = control->dwControlID;
                        details.cChannels = 1;
                        U(details).cMultipleItems = 0;
                        details.paDetails = &value;
                        details.cbDetails = sizeof(value);

                        /* restore original value */
                        rc = mixerSetControlDetails(mix, &details, MIXER_SETCONTROLDETAILSF_VALUE);
                        ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                           "MMSYSERR_NOERROR expected, got %s\n",
                           mmsys_error(rc));
                    }
                }
            }
        }
    } else {
        /* FIXME */
    }
}

static void mixer_test_deviceW(int device)
{
    MIXERCAPSW capsW;
    HMIXEROBJ mix;
    MMRESULT rc;
    DWORD d,s,ns,nc;
    char szShortName[MIXER_SHORT_NAME_CHARS];
    char szName[MIXER_LONG_NAME_CHARS];
    char szPname[MAXPNAMELEN];

    rc=mixerGetDevCapsW(device,0,sizeof(capsW));
    ok(rc==MMSYSERR_INVALPARAM,
       "mixerGetDevCapsW: MMSYSERR_INVALPARAM expected, got %s\n",
       mmsys_error(rc));

    rc=mixerGetDevCapsW(device,&capsW,4);
    ok(rc==MMSYSERR_NOERROR ||
       rc==MMSYSERR_INVALPARAM, /* Vista and W2K8 */
       "mixerGetDevCapsW: MMSYSERR_NOERROR or MMSYSERR_INVALPARAM expected, got %s\n",
       mmsys_error(rc));

    rc=mixerGetDevCapsW(device,&capsW,sizeof(capsW));
    ok(rc==MMSYSERR_NOERROR,
       "mixerGetDevCapsW: MMSYSERR_NOERROR expected, got %s\n",
       mmsys_error(rc));

    WideCharToMultiByte(CP_ACP,0,capsW.szPname, MAXPNAMELEN,szPname,
                        MAXPNAMELEN,NULL,NULL);
    if (winetest_interactive) {
        trace("  %d: \"%s\" %d.%d (%d:%d) destinations=%ld\n", device,
              szPname, capsW.vDriverVersion >> 8,
              capsW.vDriverVersion & 0xff,capsW.wMid,capsW.wPid,
              capsW.cDestinations);
    } else {
        trace("  %d: \"%s\" %d.%d (%d:%d)\n", device,
              szPname, capsW.vDriverVersion >> 8,
              capsW.vDriverVersion & 0xff,capsW.wMid,capsW.wPid);
    }


    rc = mixerOpen((HMIXER*)&mix, device, 0, 0, 0);
    ok(rc==MMSYSERR_NOERROR,
       "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",mmsys_error(rc));
    if (rc==MMSYSERR_NOERROR) {
        MIXERCAPSW capsW2;

        rc=mixerGetDevCapsW((UINT_PTR)mix,&capsW2,sizeof(capsW2));
        ok(rc==MMSYSERR_NOERROR,
           "mixerGetDevCapsW: MMSYSERR_NOERROR expected, got %s\n",
           mmsys_error(rc));
        ok(!lstrcmpW(capsW2.szPname, capsW.szPname), "Got wrong device caps\n");

        for (d=0;d<capsW.cDestinations;d++) {
            MIXERLINEW mixerlineW;
            mixerlineW.cbStruct = 0;
            mixerlineW.dwDestination=d;
            rc = mixerGetLineInfoW(mix, &mixerlineW, MIXER_GETLINEINFOF_DESTINATION);
            ok(rc==MMSYSERR_INVALPARAM,
               "mixerGetLineInfoW(MIXER_GETLINEINFOF_DESTINATION): "
               "MMSYSERR_INVALPARAM expected, got %s\n",
               mmsys_error(rc));

            mixerlineW.cbStruct = sizeof(mixerlineW);
            mixerlineW.dwDestination=capsW.cDestinations;
            rc = mixerGetLineInfoW(mix, &mixerlineW, MIXER_GETLINEINFOF_DESTINATION);
            ok(rc==MMSYSERR_INVALPARAM||rc==MIXERR_INVALLINE,
               "mixerGetLineInfoW(MIXER_GETLINEINFOF_DESTINATION): "
               "MMSYSERR_INVALPARAM or MIXERR_INVALLINE expected, got %s\n",
               mmsys_error(rc));

            mixerlineW.cbStruct = sizeof(mixerlineW);
            mixerlineW.dwDestination=d;
            rc = mixerGetLineInfoW(mix, 0, MIXER_GETLINEINFOF_DESTINATION);
            ok(rc==MMSYSERR_INVALPARAM,
               "mixerGetLineInfoW(MIXER_GETLINEINFOF_DESTINATION): "
               "MMSYSERR_INVALPARAM expected, got %s\n",
               mmsys_error(rc));

            mixerlineW.cbStruct = sizeof(mixerlineW);
            mixerlineW.dwDestination=d;
            rc = mixerGetLineInfoW(mix, &mixerlineW, -1);
            ok(rc==MMSYSERR_INVALFLAG,
               "mixerGetLineInfoW(-1): MMSYSERR_INVALFLAG expected, got %s\n",
               mmsys_error(rc));

            mixerlineW.cbStruct = sizeof(mixerlineW);
            mixerlineW.dwDestination=d;
            mixerlineW.dwUser = (ULONG_PTR)0xdeadbeef;
            rc = mixerGetLineInfoW(mix, &mixerlineW, MIXER_GETLINEINFOF_DESTINATION);
            ok(rc==MMSYSERR_NOERROR||rc==MMSYSERR_NODRIVER,
               "mixerGetLineInfoW(MIXER_GETLINEINFOF_DESTINATION): "
               "MMSYSERR_NOERROR expected, got %s\n",
               mmsys_error(rc));
            ok(mixerlineW.dwUser == 0, "dwUser was not reset\n");
            if (rc==MMSYSERR_NODRIVER)
                trace("  No Driver\n");
            else if (rc==MMSYSERR_NOERROR && winetest_interactive) {
                WideCharToMultiByte(CP_ACP,0,mixerlineW.szShortName,
                    MIXER_SHORT_NAME_CHARS,szShortName,
                    MIXER_SHORT_NAME_CHARS,NULL,NULL);
                WideCharToMultiByte(CP_ACP,0,mixerlineW.szName,
                    MIXER_LONG_NAME_CHARS,szName,
                    MIXER_LONG_NAME_CHARS,NULL,NULL);
                WideCharToMultiByte(CP_ACP,0,mixerlineW.Target.szPname,
                    MAXPNAMELEN,szPname,
                    MAXPNAMELEN,NULL, NULL);
                trace("    %ld: \"%s\" (%s) Destination=%ld Source=%ld\n",
                      d,szShortName,szName,
                      mixerlineW.dwDestination,mixerlineW.dwSource);
                trace("        LineID=%08lx Channels=%ld "
                      "Connections=%ld Controls=%ld\n",
                      mixerlineW.dwLineID,mixerlineW.cChannels,
                      mixerlineW.cConnections,mixerlineW.cControls);
                trace("        State=0x%08lx(%s)\n",
                      mixerlineW.fdwLine,line_flags(mixerlineW.fdwLine));
                trace("        ComponentType=%s\n",
                      component_type(mixerlineW.dwComponentType));
                trace("        Type=%s\n",
                      target_type(mixerlineW.Target.dwType));
                trace("        Device=%ld (%s) %d.%d (%d:%d)\n",
                      mixerlineW.Target.dwDeviceID,szPname,
                      mixerlineW.Target.vDriverVersion >> 8,
                      mixerlineW.Target.vDriverVersion & 0xff,
                      mixerlineW.Target.wMid, mixerlineW.Target.wPid);
            }
            ns=mixerlineW.cConnections;
            for(s=0;s<ns;s++) {
                mixerlineW.cbStruct = sizeof(mixerlineW);
                mixerlineW.dwDestination=d;
                mixerlineW.dwSource=s;
                rc = mixerGetLineInfoW(mix, &mixerlineW, MIXER_GETLINEINFOF_SOURCE);
                ok(rc==MMSYSERR_NOERROR||rc==MMSYSERR_NODRIVER,
                   "mixerGetLineInfoW(MIXER_GETLINEINFOF_SOURCE): "
                   "MMSYSERR_NOERROR expected, got %s\n",
                   mmsys_error(rc));
                if (rc==MMSYSERR_NODRIVER)
                    trace("  No Driver\n");
                else if (rc==MMSYSERR_NOERROR) {
                    LPMIXERCONTROLW    array;
                    MIXERLINECONTROLSW controls;
                    if (winetest_interactive) {
                        WideCharToMultiByte(CP_ACP,0,mixerlineW.szShortName,
                            MIXER_SHORT_NAME_CHARS,szShortName,
                            MIXER_SHORT_NAME_CHARS,NULL,NULL);
                        WideCharToMultiByte(CP_ACP,0,mixerlineW.szName,
                            MIXER_LONG_NAME_CHARS,szName,
                            MIXER_LONG_NAME_CHARS,NULL,NULL);
                        WideCharToMultiByte(CP_ACP,0,mixerlineW.Target.szPname,
                            MAXPNAMELEN,szPname,
                            MAXPNAMELEN,NULL, NULL);
                        trace("      %ld: \"%s\" (%s) Destination=%ld Source=%ld\n",
                              s,szShortName,szName,
                              mixerlineW.dwDestination,mixerlineW.dwSource);
                        trace("          LineID=%08lx Channels=%ld "
                              "Connections=%ld Controls=%ld\n",
                              mixerlineW.dwLineID,mixerlineW.cChannels,
                              mixerlineW.cConnections,mixerlineW.cControls);
                        trace("          State=0x%08lx(%s)\n",
                              mixerlineW.fdwLine,line_flags(mixerlineW.fdwLine));
                        trace("          ComponentType=%s\n",
                              component_type(mixerlineW.dwComponentType));
                        trace("          Type=%s\n",
                              target_type(mixerlineW.Target.dwType));
                        trace("          Device=%ld (%s) %d.%d (%d:%d)\n",
                              mixerlineW.Target.dwDeviceID,szPname,
                              mixerlineW.Target.vDriverVersion >> 8,
                              mixerlineW.Target.vDriverVersion & 0xff,
                              mixerlineW.Target.wMid, mixerlineW.Target.wPid);
                    }
                    if (mixerlineW.cControls) {
                        array=HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,
                            mixerlineW.cControls*sizeof(MIXERCONTROLW));
                        if (array) {
                            rc = mixerGetLineControlsW(mix, 0, MIXER_GETLINECONTROLSF_ALL);
                            ok(rc==MMSYSERR_INVALPARAM,
                               "mixerGetLineControlsW(MIXER_GETLINECONTROLSF_ALL): "
                               "MMSYSERR_INVALPARAM expected, got %s\n",
                               mmsys_error(rc));
                            rc = mixerGetLineControlsW(mix, &controls, -1);
                            ok(rc==MMSYSERR_INVALFLAG||rc==MMSYSERR_INVALPARAM,
                               "mixerGetLineControlsW(-1): "
                               "MMSYSERR_INVALFLAG or MMSYSERR_INVALPARAM expected, got %s\n",
                               mmsys_error(rc));

                            controls.cbStruct = sizeof(MIXERLINECONTROLSW);
                            controls.cControls = mixerlineW.cControls;
                            controls.dwLineID = mixerlineW.dwLineID;
                            controls.pamxctrl = array;
                            controls.cbmxctrl = sizeof(MIXERCONTROLW);

                            /* FIXME: do MIXER_GETLINECONTROLSF_ONEBYID
                             * and MIXER_GETLINECONTROLSF_ONEBYTYPE
                             */
                            rc = mixerGetLineControlsW(mix, &controls, MIXER_GETLINECONTROLSF_ALL);
                            ok(rc==MMSYSERR_NOERROR,
                               "mixerGetLineControlsW(MIXER_GETLINECONTROLSF_ALL): "
                               "MMSYSERR_NOERROR expected, got %s\n",
                               mmsys_error(rc));
                            if (rc==MMSYSERR_NOERROR) {
                                for(nc=0;nc<mixerlineW.cControls;nc++) {
                                    if (winetest_interactive) {
                                        WideCharToMultiByte(CP_ACP,0,array[nc].szShortName,
                                            MIXER_SHORT_NAME_CHARS,szShortName,
                                            MIXER_SHORT_NAME_CHARS,NULL,NULL);
                                        WideCharToMultiByte(CP_ACP,0,array[nc].szName,
                                            MIXER_LONG_NAME_CHARS,szName,
                                            MIXER_LONG_NAME_CHARS,NULL,NULL);
                                        trace("        %ld: \"%s\" (%s) ControlID=%ld\n", nc,
                                              szShortName, szName, array[nc].dwControlID);
                                        trace("            ControlType=%s\n",
                                               control_type(array[nc].dwControlType));
                                        trace("            Control=0x%08lx(%s)\n",
                                              array[nc].fdwControl,
                                              control_flags(array[nc].fdwControl));
                                        trace("            Items=%ld Min=%ld Max=%ld Step=%ld\n",
                                              array[nc].cMultipleItems,
                                              S1(array[nc].Bounds).dwMinimum,
                                              S1(array[nc].Bounds).dwMaximum,
                                              array[nc].Metrics.cSteps);
                                    }
                                    mixer_test_controlW(mix, &array[nc]);
                                }
                            }

                            HeapFree(GetProcessHeap(),0,array);
                        }
                    }
                }
            }
        }
        test_mixerClose((HMIXER)mix);
    }
}

static void mixer_testsA(void)
{
    MIXERCAPSA capsA;
    MMRESULT rc;
    UINT ndev, d;

    trace("--- Testing ANSI functions ---\n");

    ndev=mixerGetNumDevs();
    trace("found %d Mixer devices\n",ndev);

    rc=mixerGetDevCapsA(ndev+1,&capsA,sizeof(capsA));
    ok(rc==MMSYSERR_BADDEVICEID,
       "mixerGetDevCapsA: MMSYSERR_BADDEVICEID expected, got %s\n",
       mmsys_error(rc));

    for (d=0;d<ndev;d++)
        mixer_test_deviceA(d);
}

static void mixer_testsW(void)
{
    MIXERCAPSW capsW;
    MMRESULT rc;
    UINT ndev, d;

    trace("--- Testing WCHAR functions ---\n");

    ndev=mixerGetNumDevs();
    trace("found %d Mixer devices\n",ndev);

    rc=mixerGetDevCapsW(ndev+1,&capsW,sizeof(capsW));
    ok(rc==MMSYSERR_BADDEVICEID||rc==MMSYSERR_NOTSUPPORTED,
       "mixerGetDevCapsW: MMSYSERR_BADDEVICEID or MMSYSERR_NOTSUPPORTED "
       "expected, got %s\n", mmsys_error(rc));
    if (rc==MMSYSERR_NOTSUPPORTED)
        return;

    for (d=0;d<ndev;d++)
        mixer_test_deviceW(d);
}

static void test_mixerOpen(void)
{
    HMIXER mix;
    HANDLE event;
    MMRESULT rc;
    UINT ndev, d;

    ndev = mixerGetNumDevs();

    /* Test mixerOpen with invalid device ID values. */
    rc = mixerOpen(&mix, ndev + 1, 0, 0, 0);
    ok(rc == MMSYSERR_BADDEVICEID,
       "mixerOpen: MMSYSERR_BADDEVICEID expected, got %s\n",
       mmsys_error(rc));

    rc = mixerOpen(&mix, -1, 0, 0, 0);
    ok(rc == MMSYSERR_BADDEVICEID ||
       rc == MMSYSERR_INVALHANDLE, /* NT4/W2K */
       "mixerOpen: MMSYSERR_BADDEVICEID or MMSYSERR_INVALHANDLE expected, got %s\n",
       mmsys_error(rc));

    for (d = 0; d < ndev; d++) {
        /* Test mixerOpen with valid device ID values and invalid parameters. */
        rc = mixerOpen(&mix, d, 0, 0, CALLBACK_FUNCTION);
        ok(rc == MMSYSERR_INVALFLAG
           || rc == MMSYSERR_NOTSUPPORTED, /* 98/ME */
           "mixerOpen: MMSYSERR_INVALFLAG expected, got %s\n",
           mmsys_error(rc));

        rc = mixerOpen(&mix, d, 0xdeadbeef, 0, CALLBACK_WINDOW);
        ok(rc == MMSYSERR_INVALPARAM ||
           broken(rc == MMSYSERR_NOERROR /* 98 */),
           "mixerOpen: MMSYSERR_INVALPARAM expected, got %s\n",
           mmsys_error(rc));
        if (rc == MMSYSERR_NOERROR)
            test_mixerClose(mix);

        /* Test mixerOpen with a NULL dwCallback and CALLBACK_WINDOW flag. */
        rc = mixerOpen(&mix, d, 0, 0, CALLBACK_WINDOW);
        ok(rc == MMSYSERR_NOERROR,
           "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",
           mmsys_error(rc));
        if (rc == MMSYSERR_NOERROR)
            test_mixerClose(mix);

        rc = mixerOpen(&mix, d, 0, 0, CALLBACK_THREAD);
        ok(rc == MMSYSERR_NOERROR /* since w2k */ ||
           rc == MMSYSERR_NOTSUPPORTED, /* 98 */
           "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",
           mmsys_error(rc));
        if (rc == MMSYSERR_NOERROR)
            test_mixerClose(mix);

        rc = mixerOpen(&mix, d, 0, 0, CALLBACK_EVENT);
        ok(rc == MMSYSERR_NOERROR /* since w2k */ ||
           rc == MMSYSERR_NOTSUPPORTED, /* 98 */
           "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",
           mmsys_error(rc));
        if (rc == MMSYSERR_NOERROR)
            test_mixerClose(mix);

        event = CreateEventW(NULL, FALSE, FALSE, NULL);

        /* NOTSUPPORTED is not broken, but it enables the todo_wine marker. */
        rc = mixerOpen(&mix, d, (DWORD_PTR)event, 0, CALLBACK_EVENT);
        todo_wine
        ok(rc == MMSYSERR_NOERROR /* since w2k */ ||
           broken(rc == MMSYSERR_NOTSUPPORTED), /* 98 */
           "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",
           mmsys_error(rc));
        if (rc == MMSYSERR_NOERROR)
            test_mixerClose(mix);

        /* Test mixerOpen with normal parameters. */
        rc = mixerOpen(&mix, d, 0, 0, 0);
        ok(rc == MMSYSERR_NOERROR,
           "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",
           mmsys_error(rc));

        if (rc == MMSYSERR_NOERROR)
            test_mixerClose(mix);

        rc = WaitForSingleObject(event, 0);
        ok(rc == WAIT_TIMEOUT, "WaitEvent %d\n", rc);
        CloseHandle(event);
    }
}

START_TEST(mixer)
{
    test_mixerOpen();
    mixer_testsA();
    mixer_testsW();
}