/*
 * 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 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(HMIXER mix, LPMIXERCONTROLA 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=mixerGetControlDetails((HMIXEROBJ)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=%d\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((HMIXEROBJ)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=mixerGetControlDetails((HMIXEROBJ)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(abs(ret_value.dwValue-new_value.dwValue)<=1,
                       "Couldn't change value from %d to %d, returned %d\n",
                       value.dwValue,new_value.dwValue,ret_value.dwValue);

                    if (abs(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((HMIXEROBJ)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=mixerGetControlDetails((HMIXEROBJ)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=%d\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((HMIXEROBJ)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=mixerGetControlDetails((HMIXEROBJ)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 %d to %d, returned %d\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((HMIXEROBJ)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;
    HMIXER 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=%d\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(&mix, device, 0, 0, 0);
    ok(rc==MMSYSERR_NOERROR,
       "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",mmsys_error(rc));
    if (rc==MMSYSERR_NOERROR) {
        for (d=0;d<capsA.cDestinations;d++) {
            MIXERLINEA mixerlineA;
            mixerlineA.cbStruct = 0;
            mixerlineA.dwDestination=d;
            rc=mixerGetLineInfoA((HMIXEROBJ)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((HMIXEROBJ)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((HMIXEROBJ)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((HMIXEROBJ)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;
            rc=mixerGetLineInfoA((HMIXEROBJ)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));
            if (rc==MMSYSERR_NODRIVER)
                trace("  No Driver\n");
            else if (rc==MMSYSERR_NOERROR) {
	      if (winetest_interactive) {
                trace("    %d: \"%s\" (%s) Destination=%d Source=%d\n",
                      d,mixerlineA.szShortName, mixerlineA.szName,
                      mixerlineA.dwDestination,mixerlineA.dwSource);
                trace("        LineID=%08x Channels=%d "
                      "Connections=%d Controls=%d\n",
                      mixerlineA.dwLineID,mixerlineA.cChannels,
                      mixerlineA.cConnections,mixerlineA.cControls);
                trace("        State=0x%08x(%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=%d (%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((HMIXEROBJ)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("      %d: \"%s\" (%s) Destination=%d Source=%d\n",
                              s,mixerlineA.szShortName, mixerlineA.szName,
                              mixerlineA.dwDestination,mixerlineA.dwSource);
                        trace("          LineID=%08x Channels=%d "
                              "Connections=%d Controls=%d\n",
                              mixerlineA.dwLineID,mixerlineA.cChannels,
                              mixerlineA.cConnections,mixerlineA.cControls);
                        trace("          State=0x%08x(%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=%d (%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((HMIXEROBJ)mix,0,
                                                      MIXER_GETLINECONTROLSF_ALL);
                            ok(rc==MMSYSERR_INVALPARAM,
                               "mixerGetLineControlsA(MIXER_GETLINECONTROLSF_ALL): "
                               "MMSYSERR_INVALPARAM expected, got %s\n",
                               mmsys_error(rc));

                            rc=mixerGetLineControlsA((HMIXEROBJ)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((HMIXEROBJ)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("        %d: \"%s\" (%s) ControlID=%d\n", nc,
                                              array[nc].szShortName,
                                              array[nc].szName, array[nc].dwControlID);
                                        trace("            ControlType=%s\n",
                                               control_type(array[nc].dwControlType));
                                        trace("            Control=0x%08x(%s)\n",
                                              array[nc].fdwControl,
                                              control_flags(array[nc].fdwControl));
                                        trace("            Items=%d Min=%d Max=%d Step=%d\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(mix);
    }
}

static void mixer_test_controlW(HMIXER mix, LPMIXERCONTROLW 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=mixerGetControlDetails((HMIXEROBJ)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=%d\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((HMIXEROBJ)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=mixerGetControlDetails((HMIXEROBJ)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(abs(ret_value.dwValue-new_value.dwValue)<=1,
                       "Couldn't change value from %d to %d, returned %d\n",
                       value.dwValue,new_value.dwValue,ret_value.dwValue);

                    if (abs(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((HMIXEROBJ)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=mixerGetControlDetails((HMIXEROBJ)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=%d\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((HMIXEROBJ)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=mixerGetControlDetails((HMIXEROBJ)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 %d to %d, returned %d\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((HMIXEROBJ)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;
    HMIXER 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=%d\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(&mix, device, 0, 0, 0);
    ok(rc==MMSYSERR_NOERROR,
       "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",mmsys_error(rc));
    if (rc==MMSYSERR_NOERROR) {
        for (d=0;d<capsW.cDestinations;d++) {
            MIXERLINEW mixerlineW;
            mixerlineW.cbStruct = 0;
            mixerlineW.dwDestination=d;
            rc=mixerGetLineInfoW((HMIXEROBJ)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((HMIXEROBJ)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((HMIXEROBJ)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((HMIXEROBJ)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;
            rc=mixerGetLineInfoW((HMIXEROBJ)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));
            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("    %d: \"%s\" (%s) Destination=%d Source=%d\n",
                      d,szShortName,szName,
                      mixerlineW.dwDestination,mixerlineW.dwSource);
                trace("        LineID=%08x Channels=%d "
                      "Connections=%d Controls=%d\n",
                      mixerlineW.dwLineID,mixerlineW.cChannels,
                      mixerlineW.cConnections,mixerlineW.cControls);
                trace("        State=0x%08x(%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=%d (%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((HMIXEROBJ)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("      %d: \"%s\" (%s) Destination=%d Source=%d\n",
                              s,szShortName,szName,
                              mixerlineW.dwDestination,mixerlineW.dwSource);
                        trace("          LineID=%08x Channels=%d "
                              "Connections=%d Controls=%d\n",
                              mixerlineW.dwLineID,mixerlineW.cChannels,
                              mixerlineW.cConnections,mixerlineW.cControls);
                        trace("          State=0x%08x(%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=%d (%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((HMIXEROBJ)mix,0,
                                                     MIXER_GETLINECONTROLSF_ALL);
                            ok(rc==MMSYSERR_INVALPARAM,
                               "mixerGetLineControlsW(MIXER_GETLINECONTROLSF_ALL): "
                               "MMSYSERR_INVALPARAM expected, got %s\n",
                               mmsys_error(rc));
                            rc=mixerGetLineControlsW((HMIXEROBJ)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(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((HMIXEROBJ)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("        %d: \"%s\" (%s) ControlID=%d\n", nc,
                                              szShortName, szName, array[nc].dwControlID);
                                        trace("            ControlType=%s\n",
                                               control_type(array[nc].dwControlType));
                                        trace("            Control=0x%08x(%s)\n",
                                              array[nc].fdwControl,
                                              control_flags(array[nc].fdwControl));
                                        trace("            Items=%d Min=%d Max=%d Step=%d\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(mix);
    }
}

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

    trace("--- Testing ASCII 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;
    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,
           "mixerOpen: MMSYSERR_INVALPARAM expected, got %s\n",
           mmsys_error(rc));

        /* 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);

        /* 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);
    }
}

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