add winmm winetest
authorSylvain Petreolle <spetreolle@yahoo.fr>
Wed, 28 Oct 2009 15:15:59 +0000 (15:15 +0000)
committerSylvain Petreolle <spetreolle@yahoo.fr>
Wed, 28 Oct 2009 15:15:59 +0000 (15:15 +0000)
svn path=/trunk/; revision=43811

rostests/winetests/directory.rbuild
rostests/winetests/winmm/capture.c [new file with mode: 0644]
rostests/winetests/winmm/mci.c [new file with mode: 0644]
rostests/winetests/winmm/mixer.c [new file with mode: 0644]
rostests/winetests/winmm/mmio.c [new file with mode: 0644]
rostests/winetests/winmm/testlist.c [new file with mode: 0644]
rostests/winetests/winmm/timer.c [new file with mode: 0644]
rostests/winetests/winmm/wave.c [new file with mode: 0644]
rostests/winetests/winmm/winmm.rbuild [new file with mode: 0644]
rostests/winetests/winmm/winmm_test.h [new file with mode: 0644]

index 5cff979..f774eb8 100644 (file)
        <directory name="ws2_32">
                <xi:include href="ws2_32/ws2_32.rbuild" />
        </directory>
-       <!--
-       <directory name="winetest">
+       <!--directory name="winetest">
                <xi:include href="winetest/winetest.rbuild" />
+       </directory-->
+       <directory name="winmm">
+               <xi:include href="winmm/winmm.rbuild" />
        </directory>
-       -->
 </group>
diff --git a/rostests/winetests/winmm/capture.c b/rostests/winetests/winmm/capture.c
new file mode 100644 (file)
index 0000000..c46d35a
--- /dev/null
@@ -0,0 +1,724 @@
+/*
+ * Test winmm sound capture in each sound format
+ *
+ * Copyright (c) 2002 Francois Gouget
+ *
+ * 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
+ */
+
+#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"
+#define NOBITMAP
+#include "mmreg.h"
+
+extern GUID KSDATAFORMAT_SUBTYPE_PCM;
+extern GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+
+#include "winmm_test.h"
+
+static const char * wave_in_error(MMRESULT error)
+{
+    static char msg[1024];
+    static char long_msg[1100];
+    MMRESULT rc;
+
+    rc = waveInGetErrorText(error, msg, sizeof(msg));
+    if (rc != MMSYSERR_NOERROR)
+        sprintf(long_msg, "waveInGetErrorText(%x) failed with error %x", error, rc);
+    else
+        sprintf(long_msg, "%s(%s)", mmsys_error(error), msg);
+    return long_msg;
+}
+
+static void check_position(int device, HWAVEIN win, DWORD bytes,
+                           LPWAVEFORMATEX pwfx )
+{
+    MMTIME mmtime;
+    DWORD samples;
+    double duration;
+    MMRESULT rc;
+    DWORD returned;
+
+    samples=bytes/(pwfx->wBitsPerSample/8*pwfx->nChannels);
+    duration=((double)samples)/pwfx->nSamplesPerSec;
+
+    mmtime.wType = TIME_BYTES;
+    rc=waveInGetPosition(win, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveInGetPosition(%s): rc=%s\n",dev_name(device),wave_in_error(rc));
+    if (mmtime.wType != TIME_BYTES && winetest_debug > 1)
+        trace("waveInGetPosition(%s): TIME_BYTES not supported, returned %s\n",
+              dev_name(device),wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveInGetPosition(%s): returned %d bytes, "
+       "should be %d\n", dev_name(device), returned, bytes);
+
+    mmtime.wType = TIME_SAMPLES;
+    rc=waveInGetPosition(win, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveInGetPosition(%s): rc=%s\n",dev_name(device),wave_in_error(rc));
+    if (mmtime.wType != TIME_SAMPLES && winetest_debug > 1)
+        trace("waveInGetPosition(%s): TIME_SAMPLES not supported, "
+              "returned %s\n",dev_name(device),wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveInGetPosition(%s): returned %d samples, "
+       "should be %d\n", dev_name(device), bytes_to_samples(returned, pwfx),
+       bytes_to_samples(bytes, pwfx));
+
+    mmtime.wType = TIME_MS;
+    rc=waveInGetPosition(win, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveInGetPosition(%s): rc=%s\n",dev_name(device),wave_in_error(rc));
+    if (mmtime.wType != TIME_MS && winetest_debug > 1)
+        trace("waveInGetPosition(%s): TIME_MS not supported, returned %s\n",
+              dev_name(device), wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveInGetPosition(%s): returned %d ms, "
+       "should be %d\n", dev_name(device), bytes_to_ms(returned, pwfx),
+       bytes_to_ms(bytes, pwfx));
+
+    mmtime.wType = TIME_SMPTE;
+    rc=waveInGetPosition(win, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveInGetPosition(%s): rc=%s\n",dev_name(device),wave_in_error(rc));
+    if (mmtime.wType != TIME_SMPTE && winetest_debug > 1)
+        trace("waveInGetPosition(%s): TIME_SMPTE not supported, returned %s\n",
+              dev_name(device),wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveInGetPosition(%s): SMPTE test failed\n",
+       dev_name(device));
+
+    mmtime.wType = TIME_MIDI;
+    rc=waveInGetPosition(win, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveInGetPosition(%s): rc=%s\n",dev_name(device),wave_in_error(rc));
+    if (mmtime.wType != TIME_MIDI && winetest_debug > 1)
+        trace("waveInGetPosition(%s): TIME_MIDI not supported, returned %s\n",
+              dev_name(device),wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveInGetPosition(%s): MIDI test failed\n",
+       dev_name(device));
+
+    mmtime.wType = TIME_TICKS;
+    rc=waveInGetPosition(win, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveInGetPosition(%s): rc=%s\n",dev_name(device),wave_in_error(rc));
+    if (mmtime.wType != TIME_TICKS && winetest_debug > 1)
+        trace("waveInGetPosition(%s): TIME_TICKS not supported, returned %s\n",
+              dev_name(device),wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveInGetPosition(%s): TICKS test failed\n",
+       dev_name(device));
+}
+
+static void wave_in_test_deviceIn(int device, LPWAVEFORMATEX pwfx, DWORD format, DWORD flags, LPWAVEINCAPS pcaps)
+{
+    HWAVEIN win;
+    HANDLE hevent;
+    WAVEHDR frag;
+    MMRESULT rc;
+    DWORD res;
+    WORD nChannels = pwfx->nChannels;
+    WORD wBitsPerSample = pwfx->wBitsPerSample;
+    DWORD nSamplesPerSec = pwfx->nSamplesPerSec;
+
+    hevent=CreateEvent(NULL,FALSE,FALSE,NULL);
+    ok(hevent!=NULL,"CreateEvent(): error=%d\n",GetLastError());
+    if (hevent==NULL)
+        return;
+
+    win=NULL;
+    rc=waveInOpen(&win,device,pwfx,(DWORD_PTR)hevent,0,CALLBACK_EVENT|flags);
+    /* Note: Win9x doesn't know WAVE_FORMAT_DIRECT */
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_BADDEVICEID ||
+       rc==MMSYSERR_NOTENABLED || rc==MMSYSERR_NODRIVER ||
+       rc==MMSYSERR_ALLOCATED ||
+       ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
+       (flags & WAVE_FORMAT_DIRECT) && !(pcaps->dwFormats & format)) ||
+       ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
+       (!(flags & WAVE_FORMAT_DIRECT) || (flags & WAVE_MAPPED)) &&
+       !(pcaps->dwFormats & format)) ||
+       (rc==MMSYSERR_INVALFLAG && (flags & WAVE_FORMAT_DIRECT)),
+       "waveInOpen(%s): format=%dx%2dx%d flags=%lx(%s) rc=%s\n",
+       dev_name(device),pwfx->nSamplesPerSec,pwfx->wBitsPerSample,
+       pwfx->nChannels,CALLBACK_EVENT|flags,
+       wave_open_flags(CALLBACK_EVENT|flags),wave_in_error(rc));
+    if ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
+       (flags & WAVE_FORMAT_DIRECT) && (pcaps->dwFormats & format))
+        trace(" Reason: The device lists this format as supported in it's "
+              "capabilities but opening it failed.\n");
+    if ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
+       !(pcaps->dwFormats & format))
+        trace("waveInOpen(%s): format=%dx%2dx%d %s rc=%s failed but format "
+              "not supported so OK.\n",dev_name(device),pwfx->nSamplesPerSec,
+              pwfx->wBitsPerSample,pwfx->nChannels,
+              flags & WAVE_FORMAT_DIRECT ? "flags=WAVE_FORMAT_DIRECT" :
+              flags & WAVE_MAPPED ? "flags=WAVE_MAPPED" : "", mmsys_error(rc));
+    if (rc!=MMSYSERR_NOERROR) {
+        CloseHandle(hevent);
+        return;
+    }
+    res=WaitForSingleObject(hevent,1000);
+    ok(res==WAIT_OBJECT_0,"WaitForSingleObject failed for open\n");
+
+    ok(pwfx->nChannels==nChannels &&
+       pwfx->wBitsPerSample==wBitsPerSample &&
+       pwfx->nSamplesPerSec==nSamplesPerSec,
+       "got the wrong format: %dx%2dx%d instead of %dx%2dx%d\n",
+       pwfx->nSamplesPerSec, pwfx->wBitsPerSample,
+       pwfx->nChannels, nSamplesPerSec, wBitsPerSample, nChannels);
+
+    /* Check that the position is 0 at start */
+    check_position(device, win, 0, pwfx);
+
+    frag.lpData=HeapAlloc(GetProcessHeap(), 0, pwfx->nAvgBytesPerSec);
+    frag.dwBufferLength=pwfx->nAvgBytesPerSec;
+    frag.dwBytesRecorded=0;
+    frag.dwUser=0;
+    frag.dwFlags=0;
+    frag.dwLoops=0;
+    frag.lpNext=0;
+
+    rc=waveInPrepareHeader(win, &frag, sizeof(frag));
+    ok(rc==MMSYSERR_NOERROR, "waveInPrepareHeader(%s): rc=%s\n",
+       dev_name(device),wave_in_error(rc));
+    ok(frag.dwFlags&WHDR_PREPARED,"waveInPrepareHeader(%s): prepared flag "
+       "not set\n",dev_name(device));
+
+    if (winetest_interactive && rc==MMSYSERR_NOERROR) {
+        trace("Recording for 1 second at %5dx%2dx%d %s %s\n",
+              pwfx->nSamplesPerSec, pwfx->wBitsPerSample,pwfx->nChannels,
+              get_format_str(pwfx->wFormatTag),
+              flags & WAVE_FORMAT_DIRECT ? "WAVE_FORMAT_DIRECT" :
+              flags & WAVE_MAPPED ? "WAVE_MAPPED" : "");
+        rc=waveInAddBuffer(win, &frag, sizeof(frag));
+        ok(rc==MMSYSERR_NOERROR,"waveInAddBuffer(%s): rc=%s\n",
+           dev_name(device),wave_in_error(rc));
+
+        /* Check that the position is 0 at start */
+        check_position(device, win, 0, pwfx);
+
+        rc=waveInStart(win);
+        ok(rc==MMSYSERR_NOERROR,"waveInStart(%s): rc=%s\n",
+           dev_name(device),wave_in_error(rc));
+
+        res = WaitForSingleObject(hevent,1200);
+        ok(res==WAIT_OBJECT_0,"WaitForSingleObject failed for header\n");
+        ok(frag.dwFlags&WHDR_DONE,"WHDR_DONE not set in frag.dwFlags\n");
+        ok(frag.dwBytesRecorded==pwfx->nAvgBytesPerSec,
+           "frag.dwBytesRecorded=%d, should=%d\n",
+           frag.dwBytesRecorded,pwfx->nAvgBytesPerSec);
+
+        /* stop playing on error */
+        if (res!=WAIT_OBJECT_0) {
+            rc=waveInStop(win);
+            ok(rc==MMSYSERR_NOERROR,
+               "waveInStop(%s): rc=%s\n",dev_name(device),wave_in_error(rc));
+        }
+    }
+
+    rc=waveInUnprepareHeader(win, &frag, sizeof(frag));
+    ok(rc==MMSYSERR_NOERROR,"waveInUnprepareHeader(%s): rc=%s\n",
+       dev_name(device),wave_in_error(rc));
+
+    rc=waveInClose(win);
+    ok(rc==MMSYSERR_NOERROR,
+       "waveInClose(%s): rc=%s\n",dev_name(device),wave_in_error(rc));
+    res=WaitForSingleObject(hevent,1000);
+    ok(res==WAIT_OBJECT_0,"WaitForSingleObject failed for close\n");
+
+    if (winetest_interactive)
+    {
+        /*
+         * Now play back what we recorded
+         */
+        HWAVEOUT wout;
+
+        trace("Playing back recorded sound\n");
+        rc=waveOutOpen(&wout,WAVE_MAPPER,pwfx,(DWORD_PTR)hevent,0,CALLBACK_EVENT);
+        ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_BADDEVICEID ||
+           rc==MMSYSERR_NOTENABLED || rc==MMSYSERR_NODRIVER ||
+           rc==MMSYSERR_ALLOCATED ||
+           ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
+            !(pcaps->dwFormats & format)),
+           "waveOutOpen(%s) format=%dx%2dx%d flags=%lx(%s) rc=%s\n",
+           dev_name(device),pwfx->nSamplesPerSec,pwfx->wBitsPerSample,
+           pwfx->nChannels,CALLBACK_EVENT|flags,
+           wave_open_flags(CALLBACK_EVENT),wave_out_error(rc));
+        if (rc==MMSYSERR_NOERROR)
+        {
+            rc=waveOutPrepareHeader(wout, &frag, sizeof(frag));
+            ok(rc==MMSYSERR_NOERROR,"waveOutPrepareHeader(%s): rc=%s\n",
+               dev_name(device),wave_out_error(rc));
+
+            if (rc==MMSYSERR_NOERROR)
+            {
+                WaitForSingleObject(hevent,INFINITE);
+                rc=waveOutWrite(wout, &frag, sizeof(frag));
+                ok(rc==MMSYSERR_NOERROR,"waveOutWrite(%s): rc=%s\n",
+                   dev_name(device),wave_out_error(rc));
+                WaitForSingleObject(hevent,INFINITE);
+
+                rc=waveOutUnprepareHeader(wout, &frag, sizeof(frag));
+                ok(rc==MMSYSERR_NOERROR,"waveOutUnprepareHeader(%s): rc=%s\n",
+                   dev_name(device),wave_out_error(rc));
+            }
+            rc=waveOutClose(wout);
+            ok(rc==MMSYSERR_NOERROR,"waveOutClose(%s): rc=%s\n",
+               dev_name(device),wave_out_error(rc));
+        }
+        else
+            trace("Unable to play back the recorded sound\n");
+    }
+
+    HeapFree(GetProcessHeap(), 0, frag.lpData);
+    CloseHandle(hevent);
+}
+
+static void wave_in_test_device(UINT_PTR device)
+{
+    WAVEINCAPSA capsA;
+    WAVEINCAPSW capsW;
+    WAVEFORMATEX format,oformat;
+    WAVEFORMATEXTENSIBLE wfex;
+    HWAVEIN win;
+    MMRESULT rc;
+    UINT f;
+    WCHAR * nameW;
+    CHAR * nameA;
+    DWORD size;
+    DWORD dwPageSize;
+    BYTE * twoPages;
+    SYSTEM_INFO sSysInfo;
+    DWORD flOldProtect;
+    BOOL res;
+
+    GetSystemInfo(&sSysInfo);
+    dwPageSize = sSysInfo.dwPageSize;
+
+    rc=waveInGetDevCapsA(device,&capsA,sizeof(capsA));
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_BADDEVICEID ||
+       rc==MMSYSERR_NODRIVER,
+       "waveInGetDevCapsA(%s): failed to get capabilities: rc=%s\n",
+       dev_name(device),wave_in_error(rc));
+    if (rc==MMSYSERR_BADDEVICEID || rc==MMSYSERR_NODRIVER)
+        return;
+
+    rc=waveInGetDevCapsW(device,&capsW,sizeof(capsW));
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NOTSUPPORTED,
+       "waveInGetDevCapsW(%s): MMSYSERR_NOERROR or MMSYSERR_NOTSUPPORTED "
+       "expected, got %s\n",dev_name(device),wave_in_error(rc));
+
+    rc=waveInGetDevCapsA(device,NULL,sizeof(capsA));
+    ok(rc==MMSYSERR_INVALPARAM,
+       "waveInGetDevCapsA(%s): MMSYSERR_INVALPARAM expected, got %s\n",
+       dev_name(device),wave_in_error(rc));
+
+    rc=waveInGetDevCapsW(device,NULL,sizeof(capsW));
+    ok(rc==MMSYSERR_INVALPARAM || rc==MMSYSERR_NOTSUPPORTED,
+       "waveInGetDevCapsW(%s): MMSYSERR_INVALPARAM or MMSYSERR_NOTSUPPORTED "
+       "expected, got %s\n",dev_name(device),wave_in_error(rc));
+
+    if (0)
+    {
+    /* FIXME: this works on windows but crashes wine */
+    rc=waveInGetDevCapsA(device,(LPWAVEINCAPSA)1,sizeof(capsA));
+    ok(rc==MMSYSERR_INVALPARAM,
+       "waveInGetDevCapsA(%s): MMSYSERR_INVALPARAM expected, got %s\n",
+       dev_name(device),wave_in_error(rc));
+
+    rc=waveInGetDevCapsW(device,(LPWAVEINCAPSW)1,sizeof(capsW));
+    ok(rc==MMSYSERR_INVALPARAM ||  rc==MMSYSERR_NOTSUPPORTED,
+       "waveInGetDevCapsW(%s): MMSYSERR_INVALPARAM or MMSYSERR_NOTSUPPORTED "
+       "expected, got %s\n",dev_name(device),wave_in_error(rc));
+    }
+
+    rc=waveInGetDevCapsA(device,&capsA,4);
+    ok(rc==MMSYSERR_NOERROR,
+       "waveInGetDevCapsA(%s): MMSYSERR_NOERROR expected, got %s\n",
+       dev_name(device),wave_in_error(rc));
+
+    rc=waveInGetDevCapsW(device,&capsW,4);
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NOTSUPPORTED ||
+       rc==MMSYSERR_INVALPARAM, /* Vista, W2K8 */
+       "waveInGetDevCapsW(%s): unexpected return value %s\n",
+       dev_name(device),wave_in_error(rc));
+
+    nameA=NULL;
+    rc=waveInMessage((HWAVEIN)device, DRV_QUERYDEVICEINTERFACESIZE,
+                     (DWORD_PTR)&size, 0);
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_INVALPARAM ||
+       rc==MMSYSERR_NOTSUPPORTED,
+       "waveInMessage(%s): failed to get interface size: rc=%s\n",
+       dev_name(device),wave_in_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        nameW = HeapAlloc(GetProcessHeap(), 0, size);
+        rc=waveInMessage((HWAVEIN)device, DRV_QUERYDEVICEINTERFACE,
+                         (DWORD_PTR)nameW, size);
+        ok(rc==MMSYSERR_NOERROR,"waveInMessage(%s): failed to get interface "
+           "name: rc=%s\n",dev_name(device),wave_in_error(rc));
+        ok(lstrlenW(nameW)+1==size/sizeof(WCHAR),
+           "got an incorrect size %d\n", size);
+        if (rc==MMSYSERR_NOERROR) {
+            nameA = HeapAlloc(GetProcessHeap(), 0, size/sizeof(WCHAR));
+            WideCharToMultiByte(CP_ACP, 0, nameW, size/sizeof(WCHAR),
+                                nameA, size/sizeof(WCHAR), NULL, NULL);
+        }
+        HeapFree(GetProcessHeap(), 0, nameW);
+    } else if (rc==MMSYSERR_NOTSUPPORTED) {
+        nameA=HeapAlloc(GetProcessHeap(), 0, sizeof("not supported"));
+        strcpy(nameA, "not supported");
+    }
+
+    trace("  %s: \"%s\" (%s) %d.%d (%d:%d)\n",dev_name(device),capsA.szPname,
+          (nameA?nameA:"failed"),capsA.vDriverVersion >> 8,
+          capsA.vDriverVersion & 0xff,capsA.wMid,capsA.wPid);
+    trace("     channels=%d formats=%05x\n",
+          capsA.wChannels,capsA.dwFormats);
+
+    HeapFree(GetProcessHeap(), 0, nameA);
+
+    for (f=0;f<NB_WIN_FORMATS;f++) {
+        format.wFormatTag=WAVE_FORMAT_PCM;
+        format.nChannels=win_formats[f][3];
+        format.wBitsPerSample=win_formats[f][2];
+        format.nSamplesPerSec=win_formats[f][1];
+        format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+        format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+        format.cbSize=0;
+        wave_in_test_deviceIn(device,&format,win_formats[f][0],0, &capsA);
+        if (device != WAVE_MAPPER) {
+            wave_in_test_deviceIn(device,&format,win_formats[f][0],
+                                  WAVE_FORMAT_DIRECT, &capsA);
+            wave_in_test_deviceIn(device,&format,win_formats[f][0],
+                                  WAVE_MAPPED, &capsA);
+        }
+    }
+
+    /* Try a PCMWAVEFORMAT aligned next to an unaccessible page for bounds
+     * checking */
+    twoPages = VirtualAlloc(NULL, 2 * dwPageSize, MEM_RESERVE | MEM_COMMIT,
+                            PAGE_READWRITE);
+    ok(twoPages!=NULL,"Failed to allocate 2 pages of memory\n");
+    if (twoPages) {
+        res = VirtualProtect(twoPages + dwPageSize, dwPageSize, PAGE_NOACCESS,
+                             &flOldProtect);
+        ok(res, "Failed to set memory access on second page\n");
+        if (res) {
+            LPWAVEFORMATEX pwfx = (LPWAVEFORMATEX)(twoPages + dwPageSize -
+                sizeof(PCMWAVEFORMAT));
+            pwfx->wFormatTag=WAVE_FORMAT_PCM;
+            pwfx->nChannels=1;
+            pwfx->wBitsPerSample=8;
+            pwfx->nSamplesPerSec=22050;
+            pwfx->nBlockAlign=pwfx->nChannels*pwfx->wBitsPerSample/8;
+            pwfx->nAvgBytesPerSec=pwfx->nSamplesPerSec*pwfx->nBlockAlign;
+            wave_in_test_deviceIn(device,pwfx,WAVE_FORMAT_2M08,0, &capsA);
+            if (device != WAVE_MAPPER) {
+                wave_in_test_deviceIn(device,pwfx,WAVE_FORMAT_2M08,
+                    WAVE_FORMAT_DIRECT, &capsA);
+                wave_in_test_deviceIn(device,pwfx,WAVE_FORMAT_2M08,
+                                      WAVE_MAPPED, &capsA);
+            }
+        }
+        VirtualFree(twoPages, 2 * dwPageSize, MEM_RELEASE);
+    }
+
+    /* Testing invalid format: 2 MHz sample rate */
+    format.wFormatTag=WAVE_FORMAT_PCM;
+    format.nChannels=2;
+    format.wBitsPerSample=16;
+    format.nSamplesPerSec=2000000;
+    format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+    format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+    format.cbSize=0;
+    oformat=format;
+    rc=waveInOpen(&win,device,&format,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==WAVERR_BADFORMAT || rc==MMSYSERR_INVALFLAG ||
+       rc==MMSYSERR_INVALPARAM,
+       "waveInOpen(%s): opening the device with 2 MHz sample rate should fail: "
+       " rc=%s\n",dev_name(device),wave_in_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        trace("     got %dx%2dx%d for %dx%2dx%d\n",
+              format.nSamplesPerSec, format.wBitsPerSample,
+              format.nChannels,
+              oformat.nSamplesPerSec, oformat.wBitsPerSample,
+              oformat.nChannels);
+        waveInClose(win);
+    }
+
+    /* test non PCM formats */
+    format.wFormatTag=WAVE_FORMAT_MULAW;
+    format.nChannels=1;
+    format.wBitsPerSample=8;
+    format.nSamplesPerSec=8000;
+    format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+    format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+    format.cbSize=0;
+    rc=waveInOpen(&win,device,&format,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveInClose(win);
+        wave_in_test_deviceIn(device,&format,0,0,&capsA);
+    } else
+        trace("waveInOpen(%s): WAVE_FORMAT_MULAW not supported\n",
+              dev_name(device));
+
+    format.wFormatTag=WAVE_FORMAT_ADPCM;
+    format.nChannels=2;
+    format.wBitsPerSample=4;
+    format.nSamplesPerSec=22050;
+    format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+    format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+    format.cbSize=0;
+    rc=waveInOpen(&win,device,&format,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveInClose(win);
+        wave_in_test_deviceIn(device,&format,0,0,&capsA);
+    } else
+        trace("waveInOpen(%s): WAVE_FORMAT_ADPCM not supported\n",
+              dev_name(device));
+
+    /* test if WAVEFORMATEXTENSIBLE supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=2;
+    wfex.Format.wBitsPerSample=16;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
+    rc=waveInOpen(&win,device,&wfex.Format,0,0,
+                  CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveInClose(win);
+        wave_in_test_deviceIn(device,&wfex.Format,0,0,&capsA);
+    } else
+        trace("waveInOpen(%s): WAVE_FORMAT_EXTENSIBLE not supported\n",
+              dev_name(device));
+
+    /* test if 4 channels supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=4;
+    wfex.Format.wBitsPerSample=16;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
+    rc=waveInOpen(&win,device,&wfex.Format,0,0,
+                  CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveInClose(win);
+        wave_in_test_deviceIn(device,&wfex.Format,0,0,&capsA);
+    } else
+        trace("waveInOpen(%s): 4 channels not supported\n",
+              dev_name(device));
+
+    /* test if 6 channels supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=6;
+    wfex.Format.wBitsPerSample=16;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
+    rc=waveInOpen(&win,device,&wfex.Format,0,0,
+                  CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveInClose(win);
+        wave_in_test_deviceIn(device,&wfex.Format,0,0,&capsA);
+    } else
+        trace("waveInOpen(%s): 6 channels not supported\n",
+              dev_name(device));
+
+    if (0)
+    {
+    /* FIXME: ALSA doesn't like this */
+    /* test if 24 bit samples supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=2;
+    wfex.Format.wBitsPerSample=24;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
+    rc=waveInOpen(&win,device,&wfex.Format,0,0,
+                  CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveInClose(win);
+        wave_in_test_deviceIn(device,&wfex.Format,0,0,&capsA);
+    } else
+        trace("waveInOpen(%s): 24 bit samples not supported\n",
+              dev_name(device));
+    }
+
+    /* test if 32 bit samples supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=2;
+    wfex.Format.wBitsPerSample=32;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
+    rc=waveInOpen(&win,device,&wfex.Format,0,0,
+                  CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveInClose(win);
+        wave_in_test_deviceIn(device,&wfex.Format,0,0,&capsA);
+    } else
+        trace("waveInOpen(%s): 32 bit samples not supported\n",
+              dev_name(device));
+
+    /* test if 32 bit float samples supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=2;
+    wfex.Format.wBitsPerSample=32;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+    rc=waveInOpen(&win,device,&wfex.Format,0,0,
+                  CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveInClose(win);
+        wave_in_test_deviceIn(device,&wfex.Format,0,0,&capsA);
+    } else
+        trace("waveInOpen(%s): 32 bit float samples not supported\n",
+              dev_name(device));
+}
+
+static void wave_in_tests(void)
+{
+    WAVEINCAPSA capsA;
+    WAVEINCAPSW capsW;
+    WAVEFORMATEX format;
+    HWAVEIN win;
+    MMRESULT rc;
+    UINT ndev,d;
+
+    ndev=waveInGetNumDevs();
+    trace("found %d WaveIn devices\n",ndev);
+
+    rc=waveInGetDevCapsA(ndev+1,&capsA,sizeof(capsA));
+    ok(rc==MMSYSERR_BADDEVICEID,
+       "waveInGetDevCapsA(%s): MMSYSERR_BADDEVICEID expected, got %s\n",
+       dev_name(ndev+1),wave_in_error(rc));
+
+    rc=waveInGetDevCapsA(WAVE_MAPPER,&capsA,sizeof(capsA));
+    if (ndev>0)
+        ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NODRIVER,
+           "waveInGetDevCapsA(%s): MMSYSERR_NOERROR or MMSYSERR_NODRIVER "
+           "expected, got %s\n",dev_name(WAVE_MAPPER),wave_in_error(rc));
+    else
+        ok(rc==MMSYSERR_BADDEVICEID || rc==MMSYSERR_NODRIVER,
+           "waveInGetDevCapsA(%s): MMSYSERR_BADDEVICEID or MMSYSERR_NODRIVER "
+           "expected, got %s\n",dev_name(WAVE_MAPPER),wave_in_error(rc));
+
+    rc=waveInGetDevCapsW(ndev+1,&capsW,sizeof(capsW));
+    ok(rc==MMSYSERR_BADDEVICEID || rc==MMSYSERR_NOTSUPPORTED,
+       "waveInGetDevCapsW(%s): MMSYSERR_BADDEVICEID or MMSYSERR_NOTSUPPORTED "
+       "expected, got %s\n",dev_name(ndev+1),wave_in_error(rc));
+
+    rc=waveInGetDevCapsW(WAVE_MAPPER,&capsW,sizeof(capsW));
+    if (ndev>0)
+        ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NODRIVER ||
+           rc==MMSYSERR_NOTSUPPORTED,
+           "waveInGetDevCapsW(%s): MMSYSERR_NOERROR or MMSYSERR_NODRIVER or "
+           "MMSYSERR_NOTSUPPORTED expected, got %s\n",
+           dev_name(ndev+1),wave_in_error(rc));
+    else
+        ok(rc==MMSYSERR_BADDEVICEID || rc==MMSYSERR_NODRIVER ||
+          rc==MMSYSERR_NOTSUPPORTED,
+           "waveInGetDevCapsW(%s): MMSYSERR_BADDEVICEID or MMSYSERR_NODRIVER or"
+           "MMSYSERR_NOTSUPPORTED expected, got %s\n",
+           dev_name(ndev+1),wave_in_error(rc));
+
+    format.wFormatTag=WAVE_FORMAT_PCM;
+    format.nChannels=2;
+    format.wBitsPerSample=16;
+    format.nSamplesPerSec=44100;
+    format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+    format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+    format.cbSize=0;
+    rc=waveInOpen(&win,ndev+1,&format,0,0,CALLBACK_NULL);
+    ok(rc==MMSYSERR_BADDEVICEID,
+       "waveInOpen(%s): MMSYSERR_BADDEVICEID expected, got %s\n",
+       dev_name(ndev+1),wave_in_error(rc));
+
+    for (d=0;d<ndev;d++)
+        wave_in_test_device(d);
+
+    if (ndev>0)
+        wave_in_test_device(WAVE_MAPPER);
+}
+
+START_TEST(capture)
+{
+    wave_in_tests();
+}
diff --git a/rostests/winetests/winmm/mci.c b/rostests/winetests/winmm/mci.c
new file mode 100644 (file)
index 0000000..5f86985
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Test winmm mci
+ *
+ * Copyright 2006 Jan Zerebecki
+ *
+ * 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
+ */
+
+#include "wine/test.h"
+#include "winuser.h"
+#include "mmsystem.h"
+
+START_TEST(mci)
+{
+    int err;
+    const char command_open[] = "open new type waveaudio alias mysound";
+    const char command_close_my[] = "close mysound notify";
+    const char command_close_all[] = "close all notify";
+    const char command_sysinfo[] = "sysinfo waveaudio quantity open";
+    MSG msg;
+    char buf[1024];
+    HWND hwnd;
+
+    hwnd = CreateWindowExA(0, "static", "winmm test", WS_POPUP, 0,0,100,100,
+                           0, 0, 0, NULL);
+
+    err = mciSendString(command_open, NULL, 0, NULL);
+    ok(!err,"mciSendString(%s, NULL, 0 , NULL) returned error: %d\n", command_open, err);
+
+    err = mciSendString(command_close_my, NULL, 0, hwnd);
+    ok(!err,"mciSendString(%s, NULL, 0 , NULL) returned error: %d\n", command_close_my, err);
+
+    ok(PeekMessageA( &msg, hwnd, 0, 0, PM_REMOVE ), "PeekMessage should succeed\n");
+    ok(msg.hwnd == hwnd, "Didn't get the handle to our test window\n");
+    ok(msg.message == MM_MCINOTIFY, "got %04x instead of MM_MCINOTIFY\n", msg.message);
+    ok(msg.wParam == MCI_NOTIFY_SUCCESSFUL, "got %08lx instead of MCI_NOTIFY_SUCCESSFUL\n", msg.wParam);
+
+    err = mciSendString(command_close_all, NULL, 0, NULL);
+    todo_wine ok(!err,"mciSendString(%s, NULL, 0 , NULL) returned error: %d\n", command_close_all, err);
+
+    memset(buf, 0, sizeof(buf));
+    err = mciSendString(command_close_all, buf, sizeof(buf), hwnd);
+    todo_wine ok(!err,"mciSendString(%s, buf, sizeof(buf) , NULL) returned error: %d\n", command_close_all, err);
+    ok(buf[0] == 0, "mciSendString(%s, buf, sizeof(buf) , NULL) changed output buffer: %s\n", command_close_all, buf);
+
+    memset(buf, 0, sizeof(buf));
+    err = mciSendString(command_sysinfo, buf, sizeof(buf), NULL);
+    ok(!err,"mciSendString(%s, buf, sizeof(buf) , NULL) returned error: %d\n", command_sysinfo, err);
+    todo_wine ok(buf[0] == '0' && buf[1] == 0, "mciSendString(%s, buf, sizeof(buf) , NULL), expected output buffer '0', got: '%s'\n", command_sysinfo, buf);
+
+    err = mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, 0);
+    todo_wine ok(err == MCIERR_INVALID_DEVICE_ID ||
+        broken(!err), /* Win9x and WinMe */
+        "mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, NULL) returned %d instead of %d\n",
+        err, MCIERR_INVALID_DEVICE_ID);
+
+    DestroyWindow(hwnd);
+}
diff --git a/rostests/winetests/winmm/mixer.c b/rostests/winetests/winmm/mixer.c
new file mode 100644 (file)
index 0000000..59627a7
--- /dev/null
@@ -0,0 +1,1065 @@
+/*
+ * 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 ||
+           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);
+
+        /* 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();
+}
diff --git a/rostests/winetests/winmm/mmio.c b/rostests/winetests/winmm/mmio.c
new file mode 100644 (file)
index 0000000..79ae031
--- /dev/null
@@ -0,0 +1,453 @@
+/*
+ * Unit tests for mmio APIs
+ *
+ * Copyright 2005 Dmitry Timoshkov
+ *
+ * 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
+ */
+
+#include <assert.h>
+#include <stdarg.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "mmsystem.h"
+#include "vfw.h"
+#include "wine/test.h"
+
+static DWORD RIFF_buf[] =
+{
+    FOURCC_RIFF, 32*sizeof(DWORD), mmioFOURCC('A','V','I',' '),
+    FOURCC_LIST, 29*sizeof(DWORD), listtypeAVIHEADER, ckidAVIMAINHDR,
+    sizeof(MainAVIHeader), 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+    0xdeadbeef, 0xdeadbeef, 0xdeadbeef,0xdeadbeef,
+    0xdeadbeef, 0xdeadbeef, 0xdeadbeef,0xdeadbeef,
+    0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+    FOURCC_LIST, 10*sizeof(DWORD),listtypeSTREAMHEADER, ckidSTREAMHEADER,
+    7*sizeof(DWORD), streamtypeVIDEO, 0xdeadbeef, 0xdeadbeef,
+    0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static void test_mmioDescend(char *fname)
+{
+    MMRESULT ret;
+    HMMIO hmmio;
+    MMIOINFO mmio;
+    MMCKINFO ckRiff, ckList, ck;
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
+    mmio.cchBuffer = sizeof(RIFF_buf);
+    mmio.pchBuffer = (char *)RIFF_buf;
+    hmmio = mmioOpen(fname, &mmio, MMIO_READ);
+    if (fname && !hmmio)
+    {
+        skip("%s file is missing, skipping the test\n", fname);
+        return;
+    }
+    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+
+    /* first normal RIFF AVI parsing */
+    ret = mmioDescend(hmmio, &ckRiff, NULL, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ckRiff.ckid == FOURCC_RIFF, "wrong ckid: %04x\n", ckRiff.ckid);
+    ok(ckRiff.fccType == formtypeAVI, "wrong fccType: %04x\n", ckRiff.fccType);
+    trace("ckid %4.4s cksize %04x fccType %4.4s off %04x flags %04x\n",
+          (LPCSTR)&ckRiff.ckid, ckRiff.cksize, (LPCSTR)&ckRiff.fccType,
+          ckRiff.dwDataOffset, ckRiff.dwFlags);
+
+    ret = mmioDescend(hmmio, &ckList, &ckRiff, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ckList.ckid == FOURCC_LIST, "wrong ckid: %04x\n", ckList.ckid);
+    ok(ckList.fccType == listtypeAVIHEADER, "wrong fccType: %04x\n", ckList.fccType);
+    trace("ckid %4.4s cksize %04x fccType %4.4s off %04x flags %04x\n",
+          (LPCSTR)&ckList.ckid, ckList.cksize, (LPCSTR)&ckList.fccType,
+          ckList.dwDataOffset, ckList.dwFlags);
+
+    ret = mmioDescend(hmmio, &ck, &ckList, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ck.ckid == ckidAVIMAINHDR, "wrong ckid: %04x\n", ck.ckid);
+    ok(ck.fccType == 0, "wrong fccType: %04x\n", ck.fccType);
+    trace("ckid %4.4s cksize %04x fccType %4.4s off %04x flags %04x\n",
+          (LPCSTR)&ck.ckid, ck.cksize, (LPCSTR)&ck.fccType,
+          ck.dwDataOffset, ck.dwFlags);
+
+    /* Skip chunk data */
+    mmioSeek(hmmio, ck.cksize, SEEK_CUR);
+
+    ret = mmioDescend(hmmio, &ckList, &ckList, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ckList.ckid == FOURCC_LIST, "wrong ckid: %04x\n", ckList.ckid);
+    ok(ckList.fccType == listtypeSTREAMHEADER, "wrong fccType: %04x\n", ckList.fccType);
+    trace("ckid %4.4s cksize %04x fccType %4.4s off %04x flags %04x\n",
+          (LPCSTR)&ckList.ckid, ckList.cksize, (LPCSTR)&ckList.fccType,
+          ckList.dwDataOffset, ckList.dwFlags);
+
+    ret = mmioDescend(hmmio, &ck, &ckList, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ck.ckid == ckidSTREAMHEADER, "wrong ckid: %04x\n", ck.ckid);
+    ok(ck.fccType == 0, "wrong fccType: %04x\n", ck.fccType);
+    trace("ckid %4.4s cksize %04x fccType %4.4s off %04x flags %04x\n",
+          (LPCSTR)&ck.ckid, ck.cksize, (LPCSTR)&ck.fccType,
+          ck.dwDataOffset, ck.dwFlags);
+
+    /* test various mmioDescend flags */
+
+    mmioSeek(hmmio, 0, SEEK_SET);
+    memset(&ck, 0x66, sizeof(ck));
+    ret = mmioDescend(hmmio, &ck, NULL, MMIO_FINDRIFF);
+    ok(ret == MMIOERR_CHUNKNOTFOUND ||
+       ret == MMIOERR_INVALIDFILE, "mmioDescend returned %u\n", ret);
+
+    mmioSeek(hmmio, 0, SEEK_SET);
+    memset(&ck, 0x66, sizeof(ck));
+    ck.ckid = 0;
+    ret = mmioDescend(hmmio, &ck, NULL, MMIO_FINDRIFF);
+    ok(ret == MMIOERR_CHUNKNOTFOUND ||
+       ret == MMIOERR_INVALIDFILE, "mmioDescend returned %u\n", ret);
+
+    mmioSeek(hmmio, 0, SEEK_SET);
+    memset(&ck, 0x66, sizeof(ck));
+    ck.fccType = 0;
+    ret = mmioDescend(hmmio, &ck, NULL, MMIO_FINDRIFF);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ck.ckid == FOURCC_RIFF, "wrong ckid: %04x\n", ck.ckid);
+    ok(ck.fccType == formtypeAVI, "wrong fccType: %04x\n", ck.fccType);
+
+    mmioSeek(hmmio, 0, SEEK_SET);
+    memset(&ck, 0x66, sizeof(ck));
+    ret = mmioDescend(hmmio, &ck, NULL, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ck.ckid == FOURCC_RIFF, "wrong ckid: %04x\n", ck.ckid);
+    ok(ck.fccType == formtypeAVI, "wrong fccType: %04x\n", ck.fccType);
+
+    /* do NOT seek, use current file position */
+    memset(&ck, 0x66, sizeof(ck));
+    ck.fccType = 0;
+    ret = mmioDescend(hmmio, &ck, NULL, MMIO_FINDLIST);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ck.ckid == FOURCC_LIST, "wrong ckid: %04x\n", ck.ckid);
+    ok(ck.fccType == listtypeAVIHEADER, "wrong fccType: %04x\n", ck.fccType);
+
+    mmioSeek(hmmio, 0, SEEK_SET);
+    memset(&ck, 0x66, sizeof(ck));
+    ck.ckid = 0;
+    ck.fccType = listtypeAVIHEADER;
+    ret = mmioDescend(hmmio, &ck, NULL, MMIO_FINDCHUNK);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ck.ckid == FOURCC_RIFF, "wrong ckid: %04x\n", ck.ckid);
+    ok(ck.fccType == formtypeAVI, "wrong fccType: %04x\n", ck.fccType);
+
+    /* do NOT seek, use current file position */
+    memset(&ck, 0x66, sizeof(ck));
+    ck.ckid = FOURCC_LIST;
+    ret = mmioDescend(hmmio, &ck, NULL, MMIO_FINDCHUNK);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ck.ckid == FOURCC_LIST, "wrong ckid: %04x\n", ck.ckid);
+    ok(ck.fccType == listtypeAVIHEADER, "wrong fccType: %04x\n", ck.fccType);
+
+    mmioSeek(hmmio, 0, SEEK_SET);
+    memset(&ck, 0x66, sizeof(ck));
+    ck.ckid = FOURCC_RIFF;
+    ret = mmioDescend(hmmio, &ck, NULL, MMIO_FINDCHUNK);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ck.ckid == FOURCC_RIFF, "wrong ckid: %04x\n", ck.ckid);
+    ok(ck.fccType == formtypeAVI, "wrong fccType: %04x\n", ck.fccType);
+
+    /* do NOT seek, use current file position */
+    memset(&ckList, 0x66, sizeof(ckList));
+    ckList.ckid = 0;
+    ret = mmioDescend(hmmio, &ckList, &ck, MMIO_FINDCHUNK);
+    ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret);
+    ok(ckList.ckid == FOURCC_LIST, "wrong ckid: %04x\n", ckList.ckid);
+    ok(ckList.fccType == listtypeAVIHEADER, "wrong fccType: %04x\n", ckList.fccType);
+
+    mmioSeek(hmmio, 0, SEEK_SET);
+    memset(&ck, 0x66, sizeof(ck));
+    ret = mmioDescend(hmmio, &ck, NULL, MMIO_FINDCHUNK);
+    ok(ret == MMIOERR_CHUNKNOTFOUND ||
+       ret == MMIOERR_INVALIDFILE, "mmioDescend returned %u\n", ret);
+    ok(ck.ckid != 0x66666666, "wrong ckid: %04x\n", ck.ckid);
+    ok(ck.fccType != 0x66666666, "wrong fccType: %04x\n", ck.fccType);
+    ok(ck.dwDataOffset != 0x66666666, "wrong dwDataOffset: %04x\n", ck.dwDataOffset);
+
+    mmioSeek(hmmio, 0, SEEK_SET);
+    memset(&ck, 0x66, sizeof(ck));
+    ret = mmioDescend(hmmio, &ck, NULL, MMIO_FINDRIFF);
+    ok(ret == MMIOERR_CHUNKNOTFOUND ||
+       ret == MMIOERR_INVALIDFILE, "mmioDescend returned %u\n", ret);
+
+    mmioClose(hmmio, 0);
+}
+
+static void test_mmioOpen(char *fname)
+{
+    char buf[256];
+    MMRESULT ret;
+    HMMIO hmmio;
+    MMIOINFO mmio;
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
+    mmio.cchBuffer = sizeof(buf);
+    mmio.pchBuffer = buf;
+    hmmio = mmioOpen(fname, &mmio, MMIO_READ);
+    if (fname && !hmmio)
+    {
+        skip("%s file is missing, skipping the test\n", fname);
+        return;
+    }
+    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == MMIO_READ, "expected MMIO_READ, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == sizeof(buf), "got %u\n", mmio.cchBuffer);
+    ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer);
+
+    mmioClose(hmmio, 0);
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
+    mmio.cchBuffer = 0;
+    mmio.pchBuffer = buf;
+    hmmio = mmioOpen(fname, &mmio, MMIO_READ);
+    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == MMIO_READ, "expected MMIO_READ, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == 0, "expected 0, got %u\n", mmio.cchBuffer);
+    ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer);
+
+    mmioClose(hmmio, 0);
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
+    mmio.cchBuffer = 0;
+    mmio.pchBuffer = NULL;
+    hmmio = mmioOpen(fname, &mmio, MMIO_READ);
+    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == MMIO_READ, "expected MMIO_READ, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == 0, "expected 0, got %u\n", mmio.cchBuffer);
+    ok(mmio.pchBuffer == NULL, "expected NULL\n");
+
+    mmioClose(hmmio, 0);
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
+    mmio.cchBuffer = 256;
+    mmio.pchBuffer = NULL;
+    hmmio = mmioOpen(fname, &mmio, MMIO_READ);
+    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == (MMIO_READ|MMIO_ALLOCBUF), "expected MMIO_READ|MMIO_ALLOCBUF, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == 256, "expected 256, got %u\n", mmio.cchBuffer);
+    ok(mmio.pchBuffer != NULL, "expected not NULL\n");
+
+    mmioClose(hmmio, 0);
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
+    mmio.cchBuffer = sizeof(buf);
+    mmio.pchBuffer = buf;
+    hmmio = mmioOpen(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF);
+    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == MMIO_READ, "expected MMIO_READ, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == sizeof(buf), "got %u\n", mmio.cchBuffer);
+    ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer);
+
+    mmioClose(hmmio, 0);
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
+    mmio.cchBuffer = 0;
+    mmio.pchBuffer = NULL;
+    hmmio = mmioOpen(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF);
+    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == (MMIO_READ|MMIO_ALLOCBUF), "expected MMIO_READ|MMIO_ALLOCBUF, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == MMIO_DEFAULTBUFFER, "expected MMIO_DEFAULTBUFFER, got %u\n", mmio.cchBuffer);
+    ok(mmio.pchBuffer != NULL, "expected not NULL\n");
+
+    mmioClose(hmmio, 0);
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
+    mmio.cchBuffer = 256;
+    mmio.pchBuffer = NULL;
+    hmmio = mmioOpen(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF);
+    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == (MMIO_READ|MMIO_ALLOCBUF), "expected MMIO_READ|MMIO_ALLOCBUF, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == 256, "expected 256, got %u\n", mmio.cchBuffer);
+    ok(mmio.pchBuffer != NULL, "expected not NULL\n");
+
+    mmioClose(hmmio, 0);
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
+    mmio.cchBuffer = 0;
+    mmio.pchBuffer = buf;
+    hmmio = mmioOpen(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF);
+    if (!hmmio && mmio.wErrorRet == ERROR_BAD_FORMAT)
+    {
+        /* Seen on Win9x, WinMe but also XP-SP1 */
+        skip("Some Windows versions don't like a 0 size and a given buffer\n");
+        return;
+    }
+    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == MMIO_READ, "expected MMIO_READ, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == MMIO_DEFAULTBUFFER, "expected MMIO_DEFAULTBUFFER, got %u\n", mmio.cchBuffer);
+    ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer);
+
+    mmioClose(hmmio, 0);
+}
+
+static void test_mmioSetBuffer(char *fname)
+{
+    char buf[256];
+    MMRESULT ret;
+    HMMIO hmmio;
+    MMIOINFO mmio;
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
+    mmio.cchBuffer = sizeof(buf);
+    mmio.pchBuffer = buf;
+    hmmio = mmioOpen(fname, &mmio, MMIO_READ);
+    if (fname && !hmmio)
+    {
+        skip("%s file is missing, skipping the test\n", fname);
+        return;
+    }
+    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == MMIO_READ, "expected MMIO_READ, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == sizeof(buf), "got %u\n", mmio.cchBuffer);
+    ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer);
+
+    ret = mmioSetBuffer(hmmio, NULL, 0, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == MMIO_READ, "expected MMIO_READ, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == 0, "got not 0\n");
+    ok(mmio.pchBuffer == NULL, "got not NULL buf\n");
+
+    ret = mmioSetBuffer(hmmio, NULL, 0, MMIO_ALLOCBUF);
+    ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == MMIO_READ, "expected MMIO_READ, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == 0, "got not 0\n");
+    ok(mmio.pchBuffer == NULL, "got not NULL buf\n");
+
+    ret = mmioSetBuffer(hmmio, buf, 0, MMIO_ALLOCBUF);
+    ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == MMIO_READ, "expected MMIO_READ, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == 0, "got not 0\n");
+    ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer);
+
+    ret = mmioSetBuffer(hmmio, NULL, 256, MMIO_WRITE|MMIO_ALLOCBUF);
+    ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret);
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok(mmio.dwFlags == (MMIO_READ|MMIO_ALLOCBUF), "expected MMIO_READ|MMIO_ALLOCBUF, got %x\n", mmio.dwFlags);
+    ok(mmio.wErrorRet == MMSYSERR_NOERROR, "expected MMSYSERR_NOERROR, got %u\n", mmio.wErrorRet);
+    ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.cchBuffer == 256, "got %u\n", mmio.cchBuffer);
+    ok(mmio.pchBuffer != NULL, "expected not NULL\n");
+    ok(mmio.pchBuffer != buf, "expected != buf\n");
+
+    mmioClose(hmmio, 0);
+}
+
+START_TEST(mmio)
+{
+    char fname[] = "msrle.avi";
+
+    test_mmioDescend(NULL);
+    test_mmioDescend(fname);
+    test_mmioOpen(NULL);
+    test_mmioOpen(fname);
+    test_mmioSetBuffer(NULL);
+    test_mmioSetBuffer(fname);
+}
diff --git a/rostests/winetests/winmm/testlist.c b/rostests/winetests/winmm/testlist.c
new file mode 100644 (file)
index 0000000..e61d92d
--- /dev/null
@@ -0,0 +1,25 @@
+/* Automatically generated file; DO NOT EDIT!! */
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#define STANDALONE
+#include "wine/test.h"
+
+extern void func_capture(void);
+extern void func_mci(void);
+extern void func_mixer(void);
+extern void func_mmio(void);
+extern void func_timer(void);
+extern void func_wave(void);
+
+const struct test winetest_testlist[] =
+{
+    { "capture", func_capture },
+    { "mci", func_mci },
+    { "mixer", func_mixer },
+    { "mmio", func_mmio },
+    { "timer", func_timer },
+    { "wave", func_wave },
+    { 0, 0 }
+};
diff --git a/rostests/winetests/winmm/timer.c b/rostests/winetests/winmm/timer.c
new file mode 100644 (file)
index 0000000..7ac86b9
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * Test winmm timer
+ *
+ * Copyright (c) 2005 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
+ */
+
+#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"
+#define NOBITMAP
+#include "mmreg.h"
+
+#include "winmm_test.h"
+
+static TIMECAPS tc;
+
+static void test_timeGetDevCaps(void)
+{
+   MMRESULT rc;
+
+    rc = timeGetDevCaps(&tc, 0);
+    ok(rc == TIMERR_NOCANDO || rc == MMSYSERR_INVALPARAM,
+       "timeGetDevCaps() returned %s, should have returned TIMERR_NOCANDO "
+       "or MMSYSERR_INVALPARAM\n", mmsys_error(rc));
+
+    rc = timeGetDevCaps(0, sizeof(tc));
+    ok(rc == TIMERR_NOCANDO || rc == TIMERR_STRUCT,
+       "timeGetDevCaps() returned %s, should have returned TIMERR_NOCANDO "
+       "or TIMERR_STRUCT\n", mmsys_error(rc));
+
+    rc = timeGetDevCaps(0, 0);
+    ok(rc == TIMERR_NOCANDO || rc == MMSYSERR_INVALPARAM,
+       "timeGetDevCaps() returned %s, should have returned TIMERR_NOCANDO "
+       "or MMSYSERR_INVALPARAM\n", mmsys_error(rc));
+
+    rc = timeGetDevCaps(&tc, sizeof(tc));
+    ok(rc == TIMERR_NOERROR, "timeGetDevCaps() returned %s, "
+       "should have returned TIMERR_NOERROR\n", mmsys_error(rc));
+
+    if (rc == TIMERR_NOERROR)
+        trace("wPeriodMin = %u, wPeriodMax = %u\n",
+              tc.wPeriodMin, tc.wPeriodMax);
+}
+
+#define NUM_SAMPLES    100
+
+static DWORD count = 0;
+static DWORD times[NUM_SAMPLES];
+
+static void CALLBACK testTimeProc(UINT uID, UINT uMsg, DWORD_PTR dwUser,
+                                  DWORD_PTR dw1, DWORD_PTR dw2)
+{
+    if (count < NUM_SAMPLES)
+        times[count++] = timeGetTime();
+}
+
+static void test_timer(UINT period, UINT resolution)
+{
+    MMRESULT rc;
+    UINT i, id, delta;
+    DWORD dwMin = 0xffffffff, dwMax = 0;
+    double sum = 0.0;
+    double deviation = 0.0;
+
+    count = 0;
+
+    for (i = 0; i < NUM_SAMPLES; i++)
+        times[i] = 0;
+
+    rc = timeBeginPeriod(period);
+    ok(rc == TIMERR_NOERROR, "timeBeginPeriod(%u) returned %s, "
+       "should have returned TIMERR_NOERROR\n", period, mmsys_error(rc));
+    if (rc != TIMERR_NOERROR)
+        return;
+
+    id = timeSetEvent(period, resolution, testTimeProc, 0, TIME_PERIODIC);
+    ok(id != 0, "timeSetEvent(%u, %u, %p, 0, TIME_PERIODIC) returned %d, "
+       "should have returned id > 0\n", period, resolution, testTimeProc, id);
+    if (id == 0)
+        return;
+
+    Sleep((NUM_SAMPLES * period) + (2 * period));
+
+    rc = timeEndPeriod(period);
+    ok(rc == TIMERR_NOERROR, "timeEndPeriod(%u) returned %s, "
+       "should have returned TIMERR_NOERROR\n", period, mmsys_error(rc));
+    if (rc != TIMERR_NOERROR)
+        return;
+
+    rc = timeKillEvent(id);
+    ok(rc == TIMERR_NOERROR, "timeKillEvent(%u) returned %s, "
+       "should have returned TIMERR_NOERROR\n", id, mmsys_error(rc));
+
+    trace("period = %u, resolution = %u\n", period, resolution);
+
+    for (i = 0; i < count; i++)
+    {
+        if (i == 0)
+        {
+            if (winetest_debug > 1)
+                trace("time[%d] = %u\n", i, times[i]);
+        }
+        else
+        {
+            delta = times[i] - times[i - 1];
+
+            if (winetest_debug > 1)
+                trace("time[%d] = %u delta = %d\n", i, times[i], delta);
+
+            sum += delta;
+            deviation += ((delta - period) * (delta - period));
+
+            if (delta < dwMin)
+                dwMin = delta;
+
+            if (delta > dwMax)
+                dwMax = delta;
+        }
+    }
+
+    trace("min = %u, max = %u, average = %f, standard deviation = %f\n",
+          dwMin, dwMax, sum / (count - 1), sqrt(deviation / (count - 2)));
+}
+
+static const char * get_priority(int priority)
+{
+    static char     tmp[32];
+#define STR(x) case x: return #x
+    switch(priority) {
+    STR(THREAD_PRIORITY_LOWEST);
+    STR(THREAD_PRIORITY_BELOW_NORMAL);
+    STR(THREAD_PRIORITY_NORMAL);
+    STR(THREAD_PRIORITY_HIGHEST);
+    STR(THREAD_PRIORITY_ABOVE_NORMAL);
+    STR(THREAD_PRIORITY_TIME_CRITICAL);
+    STR(THREAD_PRIORITY_IDLE);
+    }
+    sprintf(tmp, "UNKNOWN(%d)", priority);
+    return tmp;
+}
+
+static int priority = 0;
+static BOOL fired = FALSE;
+
+static void CALLBACK priorityTimeProc(UINT uID, UINT uMsg, DWORD_PTR dwUser,
+                                      DWORD_PTR dw1, DWORD_PTR dw2)
+{
+    priority = GetThreadPriority(GetCurrentThread());
+    ok(priority!=THREAD_PRIORITY_ERROR_RETURN, "GetThreadPriority() failed, GetLastError() = %u\n", GetLastError());
+    fired = TRUE;
+}
+
+static void test_priority(void)
+{
+    UINT id;
+
+    id = timeSetEvent(100, 100, priorityTimeProc, 0, TIME_ONESHOT);
+    ok(id != 0, "timeSetEvent(100, 100, %p, 0, TIME_ONESHOT) returned %d, "
+       "should have returned id > 0\n", priorityTimeProc, id);
+    if (id == 0)
+        return;
+
+    Sleep(200);
+
+    ok(fired == TRUE, "Callback not called\n");
+    if (fired)
+    {
+        ok(priority == THREAD_PRIORITY_TIME_CRITICAL,
+           "thread priority is %s, should be THREAD_PRIORITY_TIME_CRITICAL\n",
+           get_priority(priority));
+    }
+}
+
+START_TEST(timer)
+{
+    test_timeGetDevCaps();
+
+    if (tc.wPeriodMin <= 1) {
+        test_timer(1, 0);
+        test_timer(1, 1);
+    }
+
+    if (tc.wPeriodMin <= 10) {
+        test_timer(10, 0);
+        test_timer(10, 1);
+        test_timer(10, 10);
+    }
+
+    if (tc.wPeriodMin <= 20) {
+        test_timer(20, 0);
+        test_timer(20, 1);
+        test_timer(20, 10);
+        test_timer(20, 20);
+    }
+
+    test_priority();
+}
diff --git a/rostests/winetests/winmm/wave.c b/rostests/winetests/winmm/wave.c
new file mode 100644 (file)
index 0000000..6a4786a
--- /dev/null
@@ -0,0 +1,1465 @@
+/*
+ * Test winmm sound playback in each sound format
+ *
+ * Copyright (c) 2002 Francois Gouget
+ *
+ * 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
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "wine/test.h"
+#include "windef.h"
+#include "winbase.h"
+#include "winuser.h"
+#include "winnls.h"
+#include "mmsystem.h"
+#define NOBITMAP
+#include "mmreg.h"
+#include "ks.h"
+#include "ksmedia.h"
+
+#include "winmm_test.h"
+
+static void test_multiple_waveopens(void)
+{
+    HWAVEOUT handle1, handle2;
+    MMRESULT ret;
+    WAVEFORMATEX wfx;
+
+    wfx.wFormatTag = WAVE_FORMAT_PCM;
+    wfx.nChannels = 1;
+    wfx.nSamplesPerSec = 11025;
+    wfx.nBlockAlign = 1;
+    wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
+    wfx.wBitsPerSample = 8;
+    wfx.cbSize = 0;
+
+    ret = waveOutOpen(&handle1, 0, &wfx, 0, 0, 0);
+    if (ret != MMSYSERR_NOERROR)
+    {
+        skip("Could not do the duplicate waveopen test\n");
+        return;
+    }
+
+    ret = waveOutOpen(&handle2, 0, &wfx, 0, 0, 0);
+    /* Modern Windows allows for wave-out devices to be opened multiple times.
+     * Some Wine audio drivers allow that and some don't.  To avoid false alarms
+     * for those that do, don't "todo_wine ok(...)" on success.
+     */
+    if (ret != MMSYSERR_NOERROR)
+    {
+        todo_wine ok(ret == MMSYSERR_NOERROR || broken(ret == MMSYSERR_ALLOCATED), /* winME */
+                     "second waveOutOpen returns: %x\n", ret);
+    }
+    else
+        waveOutClose(handle2);
+
+    waveOutClose(handle1);
+}
+
+/*
+ * Note that in most of this test we may get MMSYSERR_BADDEVICEID errors
+ * at about any time if the user starts another application that uses the
+ * sound device. So we should not report these as test failures.
+ *
+ * This test can play a test tone. But this only makes sense if someone
+ * is going to carefully listen to it, and would only bother everyone else.
+ * So this is only done if the test is being run in interactive mode.
+ */
+
+#define PI 3.14159265358979323846
+static char* wave_generate_la(WAVEFORMATEX* wfx, double duration, DWORD* size)
+{
+    int i,j;
+    int nb_samples;
+    char* buf;
+    char* b;
+    WAVEFORMATEXTENSIBLE *wfex = (WAVEFORMATEXTENSIBLE*)wfx;
+
+    nb_samples=(int)(duration*wfx->nSamplesPerSec);
+    *size=nb_samples*wfx->nBlockAlign;
+    b=buf=HeapAlloc(GetProcessHeap(), 0, *size);
+    for (i=0;i<nb_samples;i++) {
+        double y=sin(440.0*2*PI*i/wfx->nSamplesPerSec);
+        if (wfx->wBitsPerSample==8) {
+            unsigned char sample=(unsigned char)((double)127.5*(y+1.0));
+            for (j = 0; j < wfx->nChannels; j++)
+                *b++=sample;
+        } else if (wfx->wBitsPerSample==16) {
+            signed short sample=(signed short)((double)32767.5*y-0.5);
+            for (j = 0; j < wfx->nChannels; j++) {
+                b[0]=sample & 0xff;
+                b[1]=sample >> 8;
+                b+=2;
+            }
+        } else if (wfx->wBitsPerSample==24) {
+            signed int sample=(signed int)(((double)0x7fffff+0.5)*y-0.5);
+            for (j = 0; j < wfx->nChannels; j++) {
+                b[0]=sample & 0xff;
+                b[1]=(sample >> 8) & 0xff;
+                b[2]=(sample >> 16) & 0xff;
+                b+=3;
+            }
+        } else if ((wfx->wBitsPerSample==32) && ((wfx->wFormatTag == WAVE_FORMAT_PCM) ||
+            ((wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
+            IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)))) {
+            signed int sample=(signed int)(((double)0x7fffffff+0.5)*y-0.5);
+            for (j = 0; j < wfx->nChannels; j++) {
+                b[0]=sample & 0xff;
+                b[1]=(sample >> 8) & 0xff;
+                b[2]=(sample >> 16) & 0xff;
+                b[3]=(sample >> 24) & 0xff;
+                b+=4;
+            }
+        } else if ((wfx->wBitsPerSample==32) && (wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
+            IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) {
+            union { float f; char c[4]; } sample;
+            sample.f=(float)y;
+            for (j = 0; j < wfx->nChannels; j++) {
+                b[0]=sample.c[0];
+                b[1]=sample.c[1];
+                b[2]=sample.c[2];
+                b[3]=sample.c[3];
+                b+=4;
+            }
+        }
+    }
+    return buf;
+}
+
+static char* wave_generate_silence(WAVEFORMATEX* wfx, double duration, DWORD* size)
+{
+    int i,j;
+    int nb_samples;
+    char* buf;
+    char* b;
+    WAVEFORMATEXTENSIBLE *wfex = (WAVEFORMATEXTENSIBLE*)wfx;
+
+    nb_samples=(int)(duration*wfx->nSamplesPerSec);
+    *size=nb_samples*wfx->nBlockAlign;
+    b=buf=HeapAlloc(GetProcessHeap(), 0, *size);
+    for (i=0;i<nb_samples;i++) {
+        if (wfx->wBitsPerSample==8) {
+            for (j = 0; j < wfx->nChannels; j++)
+                *b++=128;
+        } else if (wfx->wBitsPerSample==16) {
+            for (j = 0; j < wfx->nChannels; j++) {
+                b[0]=0;
+                b[1]=0;
+                b+=2;
+            }
+        } else if (wfx->wBitsPerSample==24) {
+            for (j = 0; j < wfx->nChannels; j++) {
+                b[0]=0;
+                b[1]=0;
+                b[2]=0;
+                b+=3;
+            }
+        } else if ((wfx->wBitsPerSample==32) && ((wfx->wFormatTag == WAVE_FORMAT_PCM) ||
+            ((wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
+            IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)))) {
+            for (j = 0; j < wfx->nChannels; j++) {
+                b[0]=0;
+                b[1]=0;
+                b[2]=0;
+                b[3]=0;
+                b+=4;
+            }
+        } else if ((wfx->wBitsPerSample==32) && (wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
+            IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) {
+            union { float f; char c[4]; } sample;
+            sample.f=0;
+            for (j = 0; j < wfx->nChannels; j++) {
+                b[0]=sample.c[0];
+                b[1]=sample.c[1];
+                b[2]=sample.c[2];
+                b[3]=sample.c[3];
+                b+=4;
+            }
+        }
+    }
+    return buf;
+}
+
+const char * dev_name(int device)
+{
+    static char name[16];
+    if (device == WAVE_MAPPER)
+        return "WAVE_MAPPER";
+    sprintf(name, "%d", device);
+    return name;
+}
+
+const char* mmsys_error(MMRESULT error)
+{
+#define ERR_TO_STR(dev) case dev: return #dev
+    static char        unknown[32];
+    switch (error) {
+    ERR_TO_STR(MMSYSERR_NOERROR);
+    ERR_TO_STR(MMSYSERR_ERROR);
+    ERR_TO_STR(MMSYSERR_BADDEVICEID);
+    ERR_TO_STR(MMSYSERR_NOTENABLED);
+    ERR_TO_STR(MMSYSERR_ALLOCATED);
+    ERR_TO_STR(MMSYSERR_INVALHANDLE);
+    ERR_TO_STR(MMSYSERR_NODRIVER);
+    ERR_TO_STR(MMSYSERR_NOMEM);
+    ERR_TO_STR(MMSYSERR_NOTSUPPORTED);
+    ERR_TO_STR(MMSYSERR_BADERRNUM);
+    ERR_TO_STR(MMSYSERR_INVALFLAG);
+    ERR_TO_STR(MMSYSERR_INVALPARAM);
+    ERR_TO_STR(WAVERR_BADFORMAT);
+    ERR_TO_STR(WAVERR_STILLPLAYING);
+    ERR_TO_STR(WAVERR_UNPREPARED);
+    ERR_TO_STR(WAVERR_SYNC);
+    ERR_TO_STR(MIDIERR_UNPREPARED);
+    ERR_TO_STR(MIDIERR_STILLPLAYING);
+    ERR_TO_STR(MIDIERR_NOTREADY);
+    ERR_TO_STR(MIDIERR_NODEVICE);
+    ERR_TO_STR(MIDIERR_INVALIDSETUP);
+    ERR_TO_STR(TIMERR_NOCANDO);
+    ERR_TO_STR(TIMERR_STRUCT);
+    ERR_TO_STR(JOYERR_PARMS);
+    ERR_TO_STR(JOYERR_NOCANDO);
+    ERR_TO_STR(JOYERR_UNPLUGGED);
+    ERR_TO_STR(MIXERR_INVALLINE);
+    ERR_TO_STR(MIXERR_INVALCONTROL);
+    ERR_TO_STR(MIXERR_INVALVALUE);
+    ERR_TO_STR(MMIOERR_FILENOTFOUND);
+    ERR_TO_STR(MMIOERR_OUTOFMEMORY);
+    ERR_TO_STR(MMIOERR_CANNOTOPEN);
+    ERR_TO_STR(MMIOERR_CANNOTCLOSE);
+    ERR_TO_STR(MMIOERR_CANNOTREAD);
+    ERR_TO_STR(MMIOERR_CANNOTWRITE);
+    ERR_TO_STR(MMIOERR_CANNOTSEEK);
+    ERR_TO_STR(MMIOERR_CANNOTEXPAND);
+    ERR_TO_STR(MMIOERR_CHUNKNOTFOUND);
+    ERR_TO_STR(MMIOERR_UNBUFFERED);
+    }
+    sprintf(unknown, "Unknown(0x%08x)", error);
+    return unknown;
+#undef ERR_TO_STR
+}
+
+const char* wave_out_error(MMRESULT error)
+{
+    static char msg[1024];
+    static char long_msg[1100];
+    MMRESULT rc;
+
+    rc = waveOutGetErrorText(error, msg, sizeof(msg));
+    if (rc != MMSYSERR_NOERROR)
+        sprintf(long_msg, "waveOutGetErrorText(%x) failed with error %x",
+                error, rc);
+    else
+        sprintf(long_msg, "%s(%s)", mmsys_error(error), msg);
+    return long_msg;
+}
+
+const char * wave_open_flags(DWORD flags)
+{
+    static char msg[1024];
+    int first = TRUE;
+    msg[0] = 0;
+    if ((flags & CALLBACK_TYPEMASK) == CALLBACK_EVENT) {
+        strcat(msg, "CALLBACK_EVENT");
+        first = FALSE;
+    }
+    if ((flags & CALLBACK_TYPEMASK) == CALLBACK_FUNCTION) {
+        if (!first) strcat(msg, "|");
+        strcat(msg, "CALLBACK_FUNCTION");
+        first = FALSE;
+    }
+    if ((flags & CALLBACK_TYPEMASK) == CALLBACK_NULL) {
+        if (!first) strcat(msg, "|");
+        strcat(msg, "CALLBACK_NULL");
+        first = FALSE;
+    }
+    if ((flags & CALLBACK_TYPEMASK) == CALLBACK_THREAD) {
+        if (!first) strcat(msg, "|");
+        strcat(msg, "CALLBACK_THREAD");
+        first = FALSE;
+    }
+    if ((flags & CALLBACK_TYPEMASK) == CALLBACK_WINDOW) {
+        if (!first) strcat(msg, "|");
+        strcat(msg, "CALLBACK_WINDOW");
+        first = FALSE;
+    }
+    if ((flags & WAVE_ALLOWSYNC) == WAVE_ALLOWSYNC) {
+        if (!first) strcat(msg, "|");
+        strcat(msg, "WAVE_ALLOWSYNC");
+        first = FALSE;
+    }
+    if ((flags & WAVE_FORMAT_DIRECT) == WAVE_FORMAT_DIRECT) {
+        if (!first) strcat(msg, "|");
+        strcat(msg, "WAVE_FORMAT_DIRECT");
+        first = FALSE;
+    }
+    if ((flags & WAVE_FORMAT_QUERY) == WAVE_FORMAT_QUERY) {
+        if (!first) strcat(msg, "|");
+        strcat(msg, "WAVE_FORMAT_QUERY");
+        first = FALSE;
+    }
+    if ((flags & WAVE_MAPPED) == WAVE_MAPPED) {
+        if (!first) strcat(msg, "|");
+        strcat(msg, "WAVE_MAPPED");
+        first = FALSE;
+    }
+    return msg;
+}
+
+static const char * wave_header_flags(DWORD flags)
+{
+#define WHDR_MASK (WHDR_BEGINLOOP|WHDR_DONE|WHDR_ENDLOOP|WHDR_INQUEUE|WHDR_PREPARED)
+    static char msg[1024];
+    int first = TRUE;
+    msg[0] = 0;
+    if (flags & WHDR_BEGINLOOP) {
+        strcat(msg, "WHDR_BEGINLOOP");
+        first = FALSE;
+    }
+    if (flags & WHDR_DONE) {
+        if (!first) strcat(msg, " ");
+        strcat(msg, "WHDR_DONE");
+        first = FALSE;
+    }
+    if (flags & WHDR_ENDLOOP) {
+        if (!first) strcat(msg, " ");
+        strcat(msg, "WHDR_ENDLOOP");
+        first = FALSE;
+    }
+    if (flags & WHDR_INQUEUE) {
+        if (!first) strcat(msg, " ");
+        strcat(msg, "WHDR_INQUEUE");
+        first = FALSE;
+    }
+    if (flags & WHDR_PREPARED) {
+        if (!first) strcat(msg, " ");
+        strcat(msg, "WHDR_PREPARED");
+        first = FALSE;
+    }
+    if (flags & ~WHDR_MASK) {
+        char temp[32];
+        sprintf(temp, "UNKNOWN(0x%08x)", flags & ~WHDR_MASK);
+        if (!first) strcat(msg, " ");
+        strcat(msg, temp);
+    }
+    return msg;
+}
+
+static const char * wave_out_caps(DWORD dwSupport)
+{
+#define ADD_FLAG(f) if (dwSupport & f) strcat(msg, " " #f)
+    static char msg[256];
+    msg[0] = 0;
+
+    ADD_FLAG(WAVECAPS_PITCH);
+    ADD_FLAG(WAVECAPS_PLAYBACKRATE);
+    ADD_FLAG(WAVECAPS_VOLUME);
+    ADD_FLAG(WAVECAPS_LRVOLUME);
+    ADD_FLAG(WAVECAPS_SYNC);
+    ADD_FLAG(WAVECAPS_SAMPLEACCURATE);
+
+    return msg[0] ? msg + 1 : "";
+#undef ADD_FLAG
+}
+
+const char * wave_time_format(UINT type)
+{
+    static char msg[32];
+#define TIME_FORMAT(f) case f: return #f
+    switch (type) {
+    TIME_FORMAT(TIME_MS);
+    TIME_FORMAT(TIME_SAMPLES);
+    TIME_FORMAT(TIME_BYTES);
+    TIME_FORMAT(TIME_SMPTE);
+    TIME_FORMAT(TIME_MIDI);
+    TIME_FORMAT(TIME_TICKS);
+    }
+#undef TIME_FORMAT
+    sprintf(msg, "Unknown(0x%04x)", type);
+    return msg;
+}
+
+const char * get_format_str(WORD format)
+{
+    static char msg[32];
+#define WAVE_FORMAT(f) case f: return #f
+    switch (format) {
+    WAVE_FORMAT(WAVE_FORMAT_PCM);
+    WAVE_FORMAT(WAVE_FORMAT_ADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_IBM_CVSD);
+    WAVE_FORMAT(WAVE_FORMAT_ALAW);
+    WAVE_FORMAT(WAVE_FORMAT_MULAW);
+    WAVE_FORMAT(WAVE_FORMAT_OKI_ADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_IMA_ADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_MEDIASPACE_ADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_SIERRA_ADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_G723_ADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_DIGISTD);
+    WAVE_FORMAT(WAVE_FORMAT_DIGIFIX);
+    WAVE_FORMAT(WAVE_FORMAT_DIALOGIC_OKI_ADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_YAMAHA_ADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_SONARC);
+    WAVE_FORMAT(WAVE_FORMAT_DSPGROUP_TRUESPEECH);
+    WAVE_FORMAT(WAVE_FORMAT_ECHOSC1);
+    WAVE_FORMAT(WAVE_FORMAT_AUDIOFILE_AF36);
+    WAVE_FORMAT(WAVE_FORMAT_APTX);
+    WAVE_FORMAT(WAVE_FORMAT_AUDIOFILE_AF10);
+    WAVE_FORMAT(WAVE_FORMAT_DOLBY_AC2);
+    WAVE_FORMAT(WAVE_FORMAT_GSM610);
+    WAVE_FORMAT(WAVE_FORMAT_ANTEX_ADPCME);
+    WAVE_FORMAT(WAVE_FORMAT_CONTROL_RES_VQLPC);
+    WAVE_FORMAT(WAVE_FORMAT_DIGIREAL);
+    WAVE_FORMAT(WAVE_FORMAT_DIGIADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_CONTROL_RES_CR10);
+    WAVE_FORMAT(WAVE_FORMAT_NMS_VBXADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_G721_ADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_MPEG);
+    WAVE_FORMAT(WAVE_FORMAT_MPEGLAYER3);
+    WAVE_FORMAT(WAVE_FORMAT_CREATIVE_ADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_CREATIVE_FASTSPEECH8);
+    WAVE_FORMAT(WAVE_FORMAT_CREATIVE_FASTSPEECH10);
+    WAVE_FORMAT(WAVE_FORMAT_FM_TOWNS_SND);
+    WAVE_FORMAT(WAVE_FORMAT_OLIGSM);
+    WAVE_FORMAT(WAVE_FORMAT_OLIADPCM);
+    WAVE_FORMAT(WAVE_FORMAT_OLICELP);
+    WAVE_FORMAT(WAVE_FORMAT_OLISBC);
+    WAVE_FORMAT(WAVE_FORMAT_OLIOPR);
+    WAVE_FORMAT(WAVE_FORMAT_DEVELOPMENT);
+    WAVE_FORMAT(WAVE_FORMAT_EXTENSIBLE);
+    }
+#undef WAVE_FORMAT
+    sprintf(msg, "Unknown(0x%04x)", format);
+    return msg;
+}
+
+DWORD bytes_to_samples(DWORD bytes, LPWAVEFORMATEX pwfx)
+{
+    return bytes / pwfx->nBlockAlign;
+}
+
+DWORD bytes_to_ms(DWORD bytes, LPWAVEFORMATEX pwfx)
+{
+    return bytes_to_samples(bytes, pwfx) * 1000 / pwfx->nSamplesPerSec;
+}
+
+DWORD time_to_bytes(LPMMTIME mmtime, LPWAVEFORMATEX pwfx)
+{
+    if (mmtime->wType == TIME_BYTES)
+        return mmtime->u.cb;
+    else if (mmtime->wType == TIME_SAMPLES)
+        return mmtime->u.sample * pwfx->nBlockAlign;
+    else if (mmtime->wType == TIME_MS)
+        return mmtime->u.ms * pwfx->nAvgBytesPerSec / 1000;
+    else if (mmtime->wType == TIME_SMPTE)
+        return ((mmtime->u.smpte.hour * 60.0 * 60.0) +
+                (mmtime->u.smpte.min * 60.0) +
+                (mmtime->u.smpte.sec) +
+                (mmtime->u.smpte.frame / 30.0)) * pwfx->nAvgBytesPerSec;
+
+    trace("FIXME: time_to_bytes() type not supported\n");
+    return -1;
+}
+
+static void check_position(int device, HWAVEOUT wout, DWORD bytes,
+                           LPWAVEFORMATEX pwfx )
+{
+    MMTIME mmtime;
+    DWORD samples;
+    double duration;
+    MMRESULT rc;
+    DWORD returned;
+
+    samples=bytes/(pwfx->wBitsPerSample/8*pwfx->nChannels);
+    duration=((double)samples)/pwfx->nSamplesPerSec;
+
+    mmtime.wType = TIME_BYTES;
+    rc=waveOutGetPosition(wout, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveOutGetPosition(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
+    if (mmtime.wType != TIME_BYTES && winetest_debug > 1)
+        trace("waveOutGetPosition(%s): TIME_BYTES not supported, returned %s\n",
+              dev_name(device),wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveOutGetPosition(%s): returned %d bytes, "
+       "should be %d\n", dev_name(device), returned, bytes);
+
+    mmtime.wType = TIME_SAMPLES;
+    rc=waveOutGetPosition(wout, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveOutGetPosition(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
+    if (mmtime.wType != TIME_SAMPLES && winetest_debug > 1)
+        trace("waveOutGetPosition(%s): TIME_SAMPLES not supported, "
+              "returned %s\n",dev_name(device),wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveOutGetPosition(%s): returned %d samples "
+       "(%d bytes), should be %d (%d bytes)\n", dev_name(device),
+       bytes_to_samples(returned, pwfx), returned,
+       bytes_to_samples(bytes, pwfx), bytes);
+
+    mmtime.wType = TIME_MS;
+    rc=waveOutGetPosition(wout, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveOutGetPosition(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
+    if (mmtime.wType != TIME_MS && winetest_debug > 1)
+        trace("waveOutGetPosition(%s): TIME_MS not supported, returned %s\n",
+              dev_name(device), wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveOutGetPosition(%s): returned %d ms, "
+       "(%d bytes), should be %d (%d bytes)\n", dev_name(device),
+       bytes_to_ms(returned, pwfx), returned,
+       bytes_to_ms(bytes, pwfx), bytes);
+
+    mmtime.wType = TIME_SMPTE;
+    rc=waveOutGetPosition(wout, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveOutGetPosition(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
+    if (mmtime.wType != TIME_SMPTE && winetest_debug > 1)
+        trace("waveOutGetPosition(%s): TIME_SMPTE not supported, returned %s\n",
+              dev_name(device),wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveOutGetPosition(%s): SMPTE test failed\n",
+       dev_name(device));
+
+    mmtime.wType = TIME_MIDI;
+    rc=waveOutGetPosition(wout, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveOutGetPosition(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
+    if (mmtime.wType != TIME_MIDI && winetest_debug > 1)
+        trace("waveOutGetPosition(%s): TIME_MIDI not supported, returned %s\n",
+              dev_name(device),wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveOutGetPosition(%s): MIDI test failed\n",
+       dev_name(device));
+
+    mmtime.wType = TIME_TICKS;
+    rc=waveOutGetPosition(wout, &mmtime, sizeof(mmtime));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveOutGetPosition(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
+    if (mmtime.wType != TIME_TICKS && winetest_debug > 1)
+        trace("waveOutGetPosition(%s): TIME_TICKS not supported, returned %s\n",
+              dev_name(device),wave_time_format(mmtime.wType));
+    returned = time_to_bytes(&mmtime, pwfx);
+    ok(returned == bytes, "waveOutGetPosition(%s): TICKS test failed\n",
+       dev_name(device));
+}
+
+static void CALLBACK callback_func(HWAVEOUT hwo, UINT uMsg,
+                                   DWORD_PTR dwInstance,
+                                   DWORD dwParam1, DWORD dwParam2)
+{
+    SetEvent((HANDLE)dwInstance);
+}
+
+static DWORD WINAPI callback_thread(LPVOID lpParameter)
+{
+    MSG msg;
+
+    PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE );  /* make sure the thread has a message queue */
+    SetEvent(lpParameter);
+
+    while (GetMessage(&msg, 0, 0, 0)) {
+        UINT message = msg.message;
+        /* for some reason XP sends a WM_USER message before WOM_OPEN */
+        ok (message == WOM_OPEN || message == WOM_DONE ||
+            message == WOM_CLOSE || message == WM_USER || message == WM_APP,
+            "GetMessage returned unexpected message: %u\n", message);
+        if (message == WOM_OPEN || message == WOM_DONE || message == WOM_CLOSE)
+            SetEvent(lpParameter);
+        else if (message == WM_APP) {
+            SetEvent(lpParameter);
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+static void wave_out_test_deviceOut(int device, double duration,
+                                    int headers, int loops,
+                                    LPWAVEFORMATEX pwfx, DWORD format,
+                                    DWORD flags, LPWAVEOUTCAPS pcaps,
+                                    BOOL interactive, BOOL sine, BOOL pause)
+{
+    HWAVEOUT wout;
+    HANDLE hevent;
+    WAVEHDR *frags = 0;
+    MMRESULT rc;
+    DWORD volume;
+    WORD nChannels = pwfx->nChannels;
+    WORD wBitsPerSample = pwfx->wBitsPerSample;
+    DWORD nSamplesPerSec = pwfx->nSamplesPerSec;
+    BOOL has_volume = pcaps->dwSupport & WAVECAPS_VOLUME ? TRUE : FALSE;
+    double paused = 0.0;
+    DWORD_PTR callback = 0;
+    DWORD_PTR callback_instance = 0;
+    HANDLE thread = 0;
+    DWORD thread_id;
+    char * buffer;
+    DWORD length;
+    DWORD frag_length;
+    int i, j;
+
+    hevent=CreateEvent(NULL,FALSE,FALSE,NULL);
+    ok(hevent!=NULL,"CreateEvent(): error=%d\n",GetLastError());
+    if (hevent==NULL)
+        return;
+
+    if ((flags & CALLBACK_TYPEMASK) == CALLBACK_EVENT) {
+        callback = (DWORD_PTR)hevent;
+        callback_instance = 0;
+    } else if ((flags & CALLBACK_TYPEMASK) == CALLBACK_FUNCTION) {
+        callback = (DWORD_PTR)callback_func;
+        callback_instance = (DWORD_PTR)hevent;
+    } else if ((flags & CALLBACK_TYPEMASK) == CALLBACK_THREAD) {
+        thread = CreateThread(NULL, 0, callback_thread, hevent, 0, &thread_id);
+        if (thread) {
+            /* make sure thread is running */
+            WaitForSingleObject(hevent,10000);
+            callback = thread_id;
+            callback_instance = 0;
+        } else {
+            trace("CreateThread() failed\n");
+            CloseHandle(hevent);
+            return;
+        }
+    } else if ((flags & CALLBACK_TYPEMASK) == CALLBACK_WINDOW) {
+        trace("CALLBACK_THREAD not implemented\n");
+        CloseHandle(hevent);
+        return;
+    } else if (flags & CALLBACK_TYPEMASK) {
+        trace("Undefined callback type!\n");
+        CloseHandle(hevent);
+        return;
+    } else {
+        trace("CALLBACK_NULL not implemented\n");
+        CloseHandle(hevent);
+        return;
+    }
+    wout=NULL;
+    rc=waveOutOpen(&wout,device,pwfx,callback,callback_instance,flags);
+    /* Note: Win9x doesn't know WAVE_FORMAT_DIRECT */
+    /* It is acceptable to fail on formats that are not specified to work */
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_BADDEVICEID ||
+       rc==MMSYSERR_NOTENABLED || rc==MMSYSERR_NODRIVER ||
+       rc==MMSYSERR_ALLOCATED ||
+       ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
+       (flags & WAVE_FORMAT_DIRECT) && !(pcaps->dwFormats & format)) ||
+       ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
+       (!(flags & WAVE_FORMAT_DIRECT) || (flags & WAVE_MAPPED)) &&
+       !(pcaps->dwFormats & format)) ||
+       (rc==MMSYSERR_INVALFLAG && (flags & WAVE_FORMAT_DIRECT)),
+       "waveOutOpen(%s): format=%dx%2dx%d flags=%lx(%s) rc=%s\n",
+       dev_name(device),pwfx->nSamplesPerSec,pwfx->wBitsPerSample,
+       pwfx->nChannels,CALLBACK_EVENT|flags,
+       wave_open_flags(CALLBACK_EVENT|flags),wave_out_error(rc));
+    if ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
+       (flags & WAVE_FORMAT_DIRECT) && (pcaps->dwFormats & format))
+        trace(" Reason: The device lists this format as supported in it's "
+              "capabilities but opening it failed.\n");
+    if ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
+       !(pcaps->dwFormats & format))
+        trace("waveOutOpen(%s): format=%dx%2dx%d %s rc=%s failed but format "
+              "not supported so OK.\n", dev_name(device), pwfx->nSamplesPerSec,
+              pwfx->wBitsPerSample,pwfx->nChannels,
+              flags & WAVE_FORMAT_DIRECT ? "flags=WAVE_FORMAT_DIRECT" :
+              flags & WAVE_MAPPED ? "flags=WAVE_MAPPED" : "", mmsys_error(rc));
+    if (rc!=MMSYSERR_NOERROR)
+        goto EXIT;
+
+    WaitForSingleObject(hevent,10000);
+
+    ok(pwfx->nChannels==nChannels &&
+       pwfx->wBitsPerSample==wBitsPerSample &&
+       pwfx->nSamplesPerSec==nSamplesPerSec,
+       "got the wrong format: %dx%2dx%d instead of %dx%2dx%d\n",
+       pwfx->nSamplesPerSec, pwfx->wBitsPerSample,
+       pwfx->nChannels, nSamplesPerSec, wBitsPerSample, nChannels);
+
+    frags = HeapAlloc(GetProcessHeap(), 0, headers * sizeof(WAVEHDR));
+
+    if (sine)
+        buffer=wave_generate_la(pwfx,duration / (loops + 1),&length);
+    else
+        buffer=wave_generate_silence(pwfx,duration / (loops + 1),&length);
+
+    rc=waveOutGetVolume(wout,0);
+    ok(rc==MMSYSERR_INVALPARAM,"waveOutGetVolume(%s,0) expected "
+       "MMSYSERR_INVALPARAM, got %s\n", dev_name(device),wave_out_error(rc));
+    rc=waveOutGetVolume(wout,&volume);
+    if (rc == MMSYSERR_NOTSUPPORTED) has_volume = FALSE;
+    ok(has_volume ? rc==MMSYSERR_NOERROR : rc==MMSYSERR_NOTSUPPORTED,
+       "waveOutGetVolume(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
+
+    /* make sure fragment length is a multiple of block size */
+    frag_length = ((length / headers) / pwfx->nBlockAlign) * pwfx->nBlockAlign;
+
+    for (i = 0; i < headers; i++) {
+        frags[i].lpData=buffer + (i * frag_length);
+        if (i != (headers-1))
+            frags[i].dwBufferLength=frag_length;
+        else {
+            /* use remainder of buffer for last fragment */
+            frags[i].dwBufferLength=length - (i * frag_length);
+        }
+        frags[i].dwFlags=0;
+        frags[i].dwLoops=0;
+        rc=waveOutPrepareHeader(wout, &frags[i], sizeof(frags[0]));
+        ok(rc==MMSYSERR_NOERROR,
+           "waveOutPrepareHeader(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
+    }
+
+    if (interactive && rc==MMSYSERR_NOERROR) {
+        DWORD start;
+        trace("Playing %g second %s at %5dx%2dx%d %2d header%s %d loop%s %d bytes %s %s\n",duration,
+              sine ? "440Hz tone" : "silence",pwfx->nSamplesPerSec,
+              pwfx->wBitsPerSample,pwfx->nChannels, headers, headers > 1 ? "s": " ",
+              loops, loops == 1 ? " " : "s", length * (loops + 1),
+              get_format_str(pwfx->wFormatTag),
+              wave_open_flags(flags));
+        if (sine && has_volume && volume == 0)
+            trace("*** Warning the sound is muted, you will not hear the test\n");
+
+        /* Check that the position is 0 at start */
+        check_position(device, wout, 0, pwfx);
+
+        rc=waveOutSetVolume(wout,0x20002000);
+        ok(has_volume ? rc==MMSYSERR_NOERROR : rc==MMSYSERR_NOTSUPPORTED,
+           "waveOutSetVolume(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
+
+        rc=waveOutSetVolume(wout,volume);
+        ok(has_volume ? rc==MMSYSERR_NOERROR : rc==MMSYSERR_NOTSUPPORTED,
+           "waveOutSetVolume(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
+
+        start=GetTickCount();
+
+        rc=waveOutWrite(wout, &frags[0], sizeof(frags[0]));
+        ok(rc==MMSYSERR_NOERROR,"waveOutWrite(%s): rc=%s\n",
+           dev_name(device),wave_out_error(rc));
+
+        ok(frags[0].dwFlags==(WHDR_PREPARED|WHDR_INQUEUE),
+           "WHDR_INQUEUE WHDR_PREPARED expected, got= %s\n",
+           wave_header_flags(frags[0].dwFlags));
+
+        rc=waveOutWrite(wout, &frags[0], sizeof(frags[0]));
+        ok(rc==WAVERR_STILLPLAYING,
+           "waveOutWrite(%s): WAVE_STILLPLAYING expected, got %s\n",
+           dev_name(device),wave_out_error(rc));
+
+        ok(frags[0].dwFlags==(WHDR_PREPARED|WHDR_INQUEUE),
+           "WHDR_INQUEUE WHDR_PREPARED expected, got %s\n",
+           wave_header_flags(frags[0].dwFlags));
+
+        if (headers == 1 && loops == 0 && pause) {
+            paused = duration / 2;
+            Sleep(paused * 1000);
+            rc=waveOutPause(wout);
+            ok(rc==MMSYSERR_NOERROR,"waveOutPause(%s): rc=%s\n",
+               dev_name(device),wave_out_error(rc));
+            trace("pausing for %g seconds\n", paused);
+            Sleep(paused * 1000);
+            rc=waveOutRestart(wout);
+            ok(rc==MMSYSERR_NOERROR,"waveOutRestart(%s): rc=%s\n",
+               dev_name(device),wave_out_error(rc));
+        }
+
+        for (j = 0; j <= loops; j++) {
+            for (i = 0; i < headers; i++) {
+                /* don't do last one */
+                if (!((j == loops) && (i == (headers - 1)))) {
+                    if (j > 0)
+                        frags[(i+1) % headers].dwFlags = WHDR_PREPARED;
+                    rc=waveOutWrite(wout, &frags[(i+1) % headers], sizeof(frags[0]));
+                    ok(rc==MMSYSERR_NOERROR,"waveOutWrite(%s, header[%d]): rc=%s\n",
+                       dev_name(device),(i+1)%headers,wave_out_error(rc));
+                }
+                WaitForSingleObject(hevent,10000);
+            }
+        }
+
+        for (i = 0; i < headers; i++) {
+            ok(frags[i].dwFlags==(WHDR_DONE|WHDR_PREPARED) ||
+               broken((flags & CALLBACK_TYPEMASK)==CALLBACK_EVENT &&
+                       frags[i].dwFlags==(WHDR_DONE|WHDR_PREPARED|0x1000)), /* < NT4 */
+               "(%02d) WHDR_DONE WHDR_PREPARED expected, got %s\n",
+               i, wave_header_flags(frags[i].dwFlags));
+        }
+        check_position(device, wout, length * (loops + 1), pwfx);
+    }
+
+    for (i = 0; i < headers; i++) {
+        rc=waveOutUnprepareHeader(wout, &frags[i], sizeof(frags[0]));
+        ok(rc==MMSYSERR_NOERROR,
+           "waveOutUnprepareHeader(%s): rc=%s\n",dev_name(device),
+           wave_out_error(rc));
+    }
+    HeapFree(GetProcessHeap(), 0, buffer);
+
+    rc=waveOutClose(wout);
+    ok(rc==MMSYSERR_NOERROR,"waveOutClose(%s): rc=%s\n",dev_name(device),
+       wave_out_error(rc));
+    WaitForSingleObject(hevent,10000);
+EXIT:
+    if ((flags & CALLBACK_TYPEMASK) == CALLBACK_THREAD) {
+        PostThreadMessage(thread_id, WM_APP, 0, 0);
+        WaitForSingleObject(hevent,10000);
+    }
+    CloseHandle(hevent);
+    HeapFree(GetProcessHeap(), 0, frags);
+}
+
+static void wave_out_test_device(UINT_PTR device)
+{
+    WAVEOUTCAPSA capsA;
+    WAVEOUTCAPSW capsW;
+    WAVEFORMATEX format, oformat;
+    WAVEFORMATEXTENSIBLE wfex;
+    IMAADPCMWAVEFORMAT wfa;
+    HWAVEOUT wout;
+    MMRESULT rc;
+    UINT f;
+    WCHAR * nameW;
+    CHAR * nameA;
+    DWORD size;
+    DWORD dwPageSize;
+    BYTE * twoPages;
+    SYSTEM_INFO sSysInfo;
+    DWORD flOldProtect;
+    BOOL res;
+
+    GetSystemInfo(&sSysInfo);
+    dwPageSize = sSysInfo.dwPageSize;
+
+    rc=waveOutGetDevCapsA(device,&capsA,sizeof(capsA));
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_BADDEVICEID ||
+       rc==MMSYSERR_NODRIVER,
+       "waveOutGetDevCapsA(%s): failed to get capabilities: rc=%s\n",
+       dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_BADDEVICEID || rc==MMSYSERR_NODRIVER)
+        return;
+
+    rc=waveOutGetDevCapsW(device,&capsW,sizeof(capsW));
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NOTSUPPORTED,
+       "waveOutGetDevCapsW(%s): MMSYSERR_NOERROR or MMSYSERR_NOTSUPPORTED "
+       "expected, got %s\n",dev_name(device),wave_out_error(rc));
+
+    rc=waveOutGetDevCapsA(device,0,sizeof(capsA));
+    ok(rc==MMSYSERR_INVALPARAM,
+       "waveOutGetDevCapsA(%s): MMSYSERR_INVALPARAM expected, "
+       "got %s\n",dev_name(device),wave_out_error(rc));
+
+    rc=waveOutGetDevCapsW(device,0,sizeof(capsW));
+    ok(rc==MMSYSERR_INVALPARAM || rc==MMSYSERR_NOTSUPPORTED,
+       "waveOutGetDevCapsW(%s): MMSYSERR_INVALPARAM or MMSYSERR_NOTSUPPORTED "
+       "expected, got %s\n",dev_name(device),wave_out_error(rc));
+
+    if (0)
+    {
+    /* FIXME: this works on windows but crashes wine */
+    rc=waveOutGetDevCapsA(device,(LPWAVEOUTCAPSA)1,sizeof(capsA));
+    ok(rc==MMSYSERR_INVALPARAM,
+       "waveOutGetDevCapsA(%s): MMSYSERR_INVALPARAM expected, got %s\n",
+       dev_name(device),wave_out_error(rc));
+
+    rc=waveOutGetDevCapsW(device,(LPWAVEOUTCAPSW)1,sizeof(capsW));
+    ok(rc==MMSYSERR_INVALPARAM || rc==MMSYSERR_NOTSUPPORTED,
+       "waveOutGetDevCapsW(%s): MMSYSERR_INVALPARAM or MMSYSERR_NOTSUPPORTED "
+       "expected, got %s\n",dev_name(device),wave_out_error(rc));
+    }
+
+    rc=waveOutGetDevCapsA(device,&capsA,4);
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_INVALPARAM,
+       "waveOutGetDevCapsA(%s): MMSYSERR_NOERROR or MMSYSERR_INVALPARAM "
+       "expected, got %s\n", dev_name(device),wave_out_error(rc));
+
+    rc=waveOutGetDevCapsW(device,&capsW,4);
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NOTSUPPORTED ||
+       rc==MMSYSERR_INVALPARAM, /* Vista, W2K8 */
+       "waveOutGetDevCapsW(%s): unexpected return value %s\n",
+       dev_name(device),wave_out_error(rc));
+
+    nameA=NULL;
+    rc=waveOutMessage((HWAVEOUT)device, DRV_QUERYDEVICEINTERFACESIZE,
+                      (DWORD_PTR)&size, 0);
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_INVALPARAM ||
+       rc==MMSYSERR_NOTSUPPORTED,
+       "waveOutMessage(%s): failed to get interface size, rc=%s\n",
+       dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        nameW = HeapAlloc(GetProcessHeap(), 0, size);
+        rc=waveOutMessage((HWAVEOUT)device, DRV_QUERYDEVICEINTERFACE,
+                          (DWORD_PTR)nameW, size);
+        ok(rc==MMSYSERR_NOERROR,"waveOutMessage(%s): failed to get interface "
+           "name, rc=%s\n",dev_name(device),wave_out_error(rc));
+        ok(lstrlenW(nameW)+1==size/sizeof(WCHAR),"got an incorrect size %d\n",size);
+        if (rc==MMSYSERR_NOERROR) {
+            nameA = HeapAlloc(GetProcessHeap(), 0, size/sizeof(WCHAR));
+            WideCharToMultiByte(CP_ACP, 0, nameW, size/sizeof(WCHAR), nameA,
+                                size/sizeof(WCHAR), NULL, NULL);
+        }
+        HeapFree(GetProcessHeap(), 0, nameW);
+    }
+    else if (rc==MMSYSERR_NOTSUPPORTED) {
+        nameA=HeapAlloc(GetProcessHeap(), 0, sizeof("not supported"));
+        strcpy(nameA, "not supported");
+    }
+
+    rc=waveOutGetDevCapsA(device,&capsA,sizeof(capsA));
+    ok(rc==MMSYSERR_NOERROR,
+       "waveOutGetDevCapsA(%s): MMSYSERR_NOERROR expected, got %s\n",
+       dev_name(device),wave_out_error(rc));
+    if (rc!=MMSYSERR_NOERROR)
+        return;
+
+    trace("  %s: \"%s\" (%s) %d.%d (%d:%d)\n",dev_name(device),capsA.szPname,
+          (nameA?nameA:"failed"),capsA.vDriverVersion >> 8,
+          capsA.vDriverVersion & 0xff, capsA.wMid,capsA.wPid);
+    trace("     channels=%d formats=%05x support=%04x\n",
+          capsA.wChannels,capsA.dwFormats,capsA.dwSupport);
+    trace("     %s\n",wave_out_caps(capsA.dwSupport));
+    HeapFree(GetProcessHeap(), 0, nameA);
+
+    if (winetest_interactive && (device != WAVE_MAPPER))
+    {
+        trace("Playing a 5 seconds reference tone.\n");
+        trace("All subsequent tones should be identical to this one.\n");
+        trace("Listen for stutter, changes in pitch, volume, etc.\n");
+        format.wFormatTag=WAVE_FORMAT_PCM;
+        format.nChannels=1;
+        format.wBitsPerSample=8;
+        format.nSamplesPerSec=22050;
+        format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+        format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+        format.cbSize=0;
+
+        wave_out_test_deviceOut(device,5.0,1,0,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_EVENT,&capsA,TRUE,TRUE,FALSE);
+        wave_out_test_deviceOut(device,5.0,1,0,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_FUNCTION,&capsA,TRUE,TRUE,FALSE);
+        wave_out_test_deviceOut(device,5.0,1,0,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_THREAD,&capsA,TRUE,TRUE,FALSE);
+
+        wave_out_test_deviceOut(device,5.0,10,0,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_EVENT,&capsA,TRUE,TRUE,FALSE);
+        wave_out_test_deviceOut(device,5.0,5,1,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_EVENT,&capsA,TRUE,TRUE,FALSE);
+    } else {
+        format.wFormatTag=WAVE_FORMAT_PCM;
+        format.nChannels=1;
+        format.wBitsPerSample=8;
+        format.nSamplesPerSec=22050;
+        format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+        format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+        format.cbSize=0;
+        wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_EVENT,&capsA,TRUE,FALSE,FALSE);
+        wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_EVENT,&capsA,TRUE,FALSE,TRUE);
+        wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_FUNCTION,&capsA,TRUE,FALSE,FALSE);
+        wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_FUNCTION,&capsA,TRUE,FALSE,TRUE);
+        wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_THREAD,&capsA,TRUE,FALSE,FALSE);
+        wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_THREAD,&capsA,TRUE,FALSE,TRUE);
+
+        wave_out_test_deviceOut(device,1.0,10,0,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_EVENT,&capsA,TRUE,FALSE,FALSE);
+        wave_out_test_deviceOut(device,1.0,5,1,&format,WAVE_FORMAT_2M08,
+                                CALLBACK_EVENT,&capsA,TRUE,FALSE,FALSE);
+    }
+
+    for (f=0;f<NB_WIN_FORMATS;f++) {
+        format.wFormatTag=WAVE_FORMAT_PCM;
+        format.nChannels=win_formats[f][3];
+        format.wBitsPerSample=win_formats[f][2];
+        format.nSamplesPerSec=win_formats[f][1];
+        format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+        format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+        format.cbSize=0;
+        wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                CALLBACK_FUNCTION,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                CALLBACK_THREAD,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+
+        wave_out_test_deviceOut(device,1.0,10,0,&format,win_formats[f][0],
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,5,1,&format,win_formats[f][0],
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+
+        if (winetest_interactive) {
+            wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                    CALLBACK_EVENT,&capsA,winetest_interactive,
+                                    TRUE,TRUE);
+            wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                    CALLBACK_FUNCTION,&capsA,winetest_interactive,
+                                    TRUE,TRUE);
+            wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                    CALLBACK_THREAD,&capsA,winetest_interactive,
+                                    TRUE,TRUE);
+
+            wave_out_test_deviceOut(device,1.0,10,0,&format,win_formats[f][0],
+                                    CALLBACK_EVENT,&capsA,winetest_interactive,
+                                    TRUE,TRUE);
+            wave_out_test_deviceOut(device,1.0,5,1,&format,win_formats[f][0],
+                                    CALLBACK_EVENT,&capsA,winetest_interactive,
+                                    TRUE,TRUE);
+        }
+        if (device != WAVE_MAPPER)
+        {
+            wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                    CALLBACK_EVENT|WAVE_FORMAT_DIRECT,&capsA,
+                                    winetest_interactive,TRUE,FALSE);
+            wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                    CALLBACK_EVENT|WAVE_MAPPED,&capsA,
+                                    winetest_interactive,TRUE,FALSE);
+            wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                    CALLBACK_FUNCTION|WAVE_FORMAT_DIRECT,&capsA,
+                                    winetest_interactive,TRUE,FALSE);
+            wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                    CALLBACK_FUNCTION|WAVE_MAPPED,&capsA,
+                                    winetest_interactive,TRUE,FALSE);
+            wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                    CALLBACK_THREAD|WAVE_FORMAT_DIRECT,&capsA,
+                                    winetest_interactive,TRUE,FALSE);
+            wave_out_test_deviceOut(device,1.0,1,0,&format,win_formats[f][0],
+                                    CALLBACK_THREAD|WAVE_MAPPED,&capsA,
+                                    winetest_interactive,TRUE,FALSE);
+
+            wave_out_test_deviceOut(device,1.0,10,0,&format,win_formats[f][0],
+                                    CALLBACK_EVENT|WAVE_FORMAT_DIRECT,&capsA,
+                                    winetest_interactive,TRUE,FALSE);
+            wave_out_test_deviceOut(device,1.0,5,1,&format,win_formats[f][0],
+                                    CALLBACK_EVENT|WAVE_FORMAT_DIRECT,&capsA,
+                                    winetest_interactive,TRUE,FALSE);
+        }
+    }
+
+    /* Try a PCMWAVEFORMAT aligned next to an unaccessible page for bounds
+     * checking */
+    twoPages = VirtualAlloc(NULL, 2 * dwPageSize, MEM_RESERVE | MEM_COMMIT,
+                            PAGE_READWRITE);
+    ok(twoPages!=NULL,"Failed to allocate 2 pages of memory\n");
+    if (twoPages) {
+        res = VirtualProtect(twoPages + dwPageSize, dwPageSize, PAGE_NOACCESS,
+                             &flOldProtect);
+        ok(res, "Failed to set memory access on second page\n");
+        if (res) {
+            LPWAVEFORMATEX pwfx = (LPWAVEFORMATEX)(twoPages + dwPageSize -
+                sizeof(PCMWAVEFORMAT));
+            pwfx->wFormatTag=WAVE_FORMAT_PCM;
+            pwfx->nChannels=1;
+            pwfx->wBitsPerSample=8;
+            pwfx->nSamplesPerSec=22050;
+            pwfx->nBlockAlign=pwfx->nChannels*pwfx->wBitsPerSample/8;
+            pwfx->nAvgBytesPerSec=pwfx->nSamplesPerSec*pwfx->nBlockAlign;
+            wave_out_test_deviceOut(device,1.0,1,0,pwfx,WAVE_FORMAT_2M08,
+                                    CALLBACK_EVENT,&capsA,winetest_interactive,
+                                    TRUE,FALSE);
+            wave_out_test_deviceOut(device,1.0,10,0,pwfx,WAVE_FORMAT_2M08,
+                                    CALLBACK_EVENT,&capsA,winetest_interactive,
+                                    TRUE,FALSE);
+            wave_out_test_deviceOut(device,1.0,5,1,pwfx,WAVE_FORMAT_2M08,
+                                    CALLBACK_EVENT,&capsA,winetest_interactive,
+                                    TRUE,FALSE);
+            if (device != WAVE_MAPPER)
+            {
+                wave_out_test_deviceOut(device,1.0,1,0,pwfx,WAVE_FORMAT_2M08,
+                                        CALLBACK_EVENT|WAVE_FORMAT_DIRECT,
+                                        &capsA,winetest_interactive,TRUE,FALSE);
+                wave_out_test_deviceOut(device,1.0,1,0,pwfx,WAVE_FORMAT_2M08,
+                                        CALLBACK_EVENT|WAVE_MAPPED,&capsA,
+                                        winetest_interactive,TRUE,FALSE);
+                wave_out_test_deviceOut(device,1.0,10,0,pwfx,WAVE_FORMAT_2M08,
+                                        CALLBACK_EVENT|WAVE_FORMAT_DIRECT,
+                                        &capsA,winetest_interactive,TRUE,FALSE);
+                wave_out_test_deviceOut(device,1.0,10,0,pwfx,WAVE_FORMAT_2M08,
+                                        CALLBACK_EVENT|WAVE_MAPPED,&capsA,
+                                        winetest_interactive,TRUE,FALSE);
+                wave_out_test_deviceOut(device,1.0,5,1,pwfx,WAVE_FORMAT_2M08,
+                                        CALLBACK_EVENT|WAVE_FORMAT_DIRECT,
+                                        &capsA,winetest_interactive,TRUE,FALSE);
+                wave_out_test_deviceOut(device,1.0,5,1,pwfx,WAVE_FORMAT_2M08,
+                                        CALLBACK_EVENT|WAVE_MAPPED,&capsA,
+                                        winetest_interactive,TRUE,FALSE);
+            }
+        }
+        VirtualFree(twoPages, 0, MEM_RELEASE);
+    }
+
+    /* Testing invalid format: 11 bits per sample */
+    format.wFormatTag=WAVE_FORMAT_PCM;
+    format.nChannels=2;
+    format.wBitsPerSample=11;
+    format.nSamplesPerSec=22050;
+    format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+    format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+    format.cbSize=0;
+    oformat=format;
+    rc=waveOutOpen(&wout,device,&format,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==WAVERR_BADFORMAT || rc==MMSYSERR_INVALFLAG ||
+       rc==MMSYSERR_INVALPARAM,
+       "waveOutOpen(%s): opening the device in 11 bits mode should fail: "
+       "rc=%s\n",dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        trace("     got %dx%2dx%d for %dx%2dx%d\n",
+              format.nSamplesPerSec, format.wBitsPerSample,
+              format.nChannels,
+              oformat.nSamplesPerSec, oformat.wBitsPerSample,
+              oformat.nChannels);
+        waveOutClose(wout);
+    }
+
+    /* Testing invalid format: 2 MHz sample rate */
+    format.wFormatTag=WAVE_FORMAT_PCM;
+    format.nChannels=2;
+    format.wBitsPerSample=16;
+    format.nSamplesPerSec=2000000;
+    format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+    format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+    format.cbSize=0;
+    oformat=format;
+    rc=waveOutOpen(&wout,device,&format,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==WAVERR_BADFORMAT || rc==MMSYSERR_INVALFLAG ||
+       rc==MMSYSERR_INVALPARAM,
+       "waveOutOpen(%s): opening the device at 2 MHz sample rate should fail: "
+       "rc=%s\n",dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        trace("     got %dx%2dx%d for %dx%2dx%d\n",
+              format.nSamplesPerSec, format.wBitsPerSample,
+              format.nChannels,
+              oformat.nSamplesPerSec, oformat.wBitsPerSample,
+              oformat.nChannels);
+        waveOutClose(wout);
+    }
+
+    /* try some non PCM formats */
+    format.wFormatTag=WAVE_FORMAT_MULAW;
+    format.nChannels=1;
+    format.wBitsPerSample=8;
+    format.nSamplesPerSec=8000;
+    format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+    format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+    format.cbSize=0;
+    rc=waveOutOpen(&wout,device,&format,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR ||rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveOutOpen(%s): returned %s\n",dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveOutClose(wout);
+        wave_out_test_deviceOut(device,1.0,1,0,&format,0,CALLBACK_EVENT,
+                                &capsA,winetest_interactive,TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,10,0,&format,0,CALLBACK_EVENT,
+                                &capsA,winetest_interactive,TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,5,1,&format,0,CALLBACK_EVENT,
+                                &capsA,winetest_interactive,TRUE,FALSE);
+    } else
+        trace("waveOutOpen(%s): WAVE_FORMAT_MULAW not supported\n",
+              dev_name(device));
+
+    wfa.wfx.wFormatTag=WAVE_FORMAT_IMA_ADPCM;
+    wfa.wfx.nChannels=1;
+    wfa.wfx.nSamplesPerSec=11025;
+    wfa.wfx.nAvgBytesPerSec=5588;
+    wfa.wfx.nBlockAlign=256;
+    wfa.wfx.wBitsPerSample=4; /* see imaadp32.c */
+    wfa.wfx.cbSize=2;
+    wfa.wSamplesPerBlock=505;
+    rc=waveOutOpen(&wout,device,&wfa.wfx,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR ||rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveOutOpen(%s): returned %s\n",dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveOutClose(wout);
+        /* TODO: teach wave_generate_* ADPCM
+        wave_out_test_deviceOut(device,1.0,1,0,&wfa.wfx,0,CALLBACK_EVENT,
+                                &capsA,winetest_interactive,TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,10,0,&wfa.wfx,0,CALLBACK_EVENT,
+                                &capsA,winetest_interactive,TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,5,1,&wfa.wfx,0,CALLBACK_EVENT,
+                                &capsA,winetest_interactive,TRUE,FALSE);
+       */
+    } else
+        trace("waveOutOpen(%s): WAVE_FORMAT_IMA_ADPCM not supported\n",
+              dev_name(device));
+
+    /* test if WAVEFORMATEXTENSIBLE supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=2;
+    wfex.Format.wBitsPerSample=16;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
+    rc=waveOutOpen(&wout,device,&wfex.Format,0,0,
+                   CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveOutOpen(%s): returned %s\n",dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveOutClose(wout);
+        wave_out_test_deviceOut(device,1.0,1,0,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,10,0,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,5,1,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+    } else
+        trace("waveOutOpen(%s): WAVE_FORMAT_EXTENSIBLE not supported\n",
+              dev_name(device));
+
+    /* test if 4 channels supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=4;
+    wfex.Format.wBitsPerSample=16;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
+    rc=waveOutOpen(&wout,device,&wfex.Format,0,0,
+                   CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveOutOpen(%s): returned %s\n",dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveOutClose(wout);
+        wave_out_test_deviceOut(device,1.0,1,0,&wfex.Format,0,CALLBACK_EVENT,
+                                &capsA,winetest_interactive,TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,10,0,&wfex.Format,0,CALLBACK_EVENT,
+                                &capsA,winetest_interactive,TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,5,1,&wfex.Format,0,CALLBACK_EVENT,
+                                &capsA,winetest_interactive,TRUE,FALSE);
+    } else
+        trace("waveOutOpen(%s): 4 channels not supported\n",
+              dev_name(device));
+
+    /* test if 6 channels supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=6;
+    wfex.Format.wBitsPerSample=16;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
+    rc=waveOutOpen(&wout,device,&wfex.Format,0,0,
+                   CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveOutOpen(%s): returned %s\n",dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveOutClose(wout);
+        wave_out_test_deviceOut(device,1.0,1,0,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,10,0,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,5,1,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+    } else
+        trace("waveOutOpen(%s): 6 channels not supported\n",
+              dev_name(device));
+
+    if (0)
+    {
+    /* FIXME: ALSA doesn't like this format */
+    /* test if 24 bit samples supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=2;
+    wfex.Format.wBitsPerSample=24;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
+    rc=waveOutOpen(&wout,device,&wfex.Format,0,0,
+                   CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveOutOpen(%s): returned %s\n",dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveOutClose(wout);
+        wave_out_test_deviceOut(device,1.0,1,0,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+    } else
+        trace("waveOutOpen(%s): 24 bit samples not supported\n",
+              dev_name(device));
+    }
+
+    /* test if 32 bit samples supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=2;
+    wfex.Format.wBitsPerSample=32;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
+    rc=waveOutOpen(&wout,device,&wfex.Format,0,0,
+                   CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveOutOpen(%s): returned %s\n",dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveOutClose(wout);
+        wave_out_test_deviceOut(device,1.0,1,0,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,10,0,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,5,1,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+    } else
+        trace("waveOutOpen(%s): 32 bit samples not supported\n",
+              dev_name(device));
+
+    /* test if 32 bit float samples supported */
+    wfex.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE;
+    wfex.Format.nChannels=2;
+    wfex.Format.wBitsPerSample=32;
+    wfex.Format.nSamplesPerSec=22050;
+    wfex.Format.nBlockAlign=wfex.Format.nChannels*wfex.Format.wBitsPerSample/8;
+    wfex.Format.nAvgBytesPerSec=wfex.Format.nSamplesPerSec*
+        wfex.Format.nBlockAlign;
+    wfex.Format.cbSize=22;
+    wfex.Samples.wValidBitsPerSample=wfex.Format.wBitsPerSample;
+    wfex.dwChannelMask=SPEAKER_ALL;
+    wfex.SubFormat=KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
+    rc=waveOutOpen(&wout,device,&wfex.Format,0,0,
+                   CALLBACK_NULL|WAVE_FORMAT_DIRECT);
+    ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT ||
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM,
+       "waveOutOpen(%s): returned %s\n",dev_name(device),wave_out_error(rc));
+    if (rc==MMSYSERR_NOERROR) {
+        waveOutClose(wout);
+        wave_out_test_deviceOut(device,1.0,1,0,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,10,0,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+        wave_out_test_deviceOut(device,1.0,5,1,&wfex.Format,WAVE_FORMAT_2M16,
+                                CALLBACK_EVENT,&capsA,winetest_interactive,
+                                TRUE,FALSE);
+    } else
+        trace("waveOutOpen(%s): 32 bit float samples not supported\n",
+              dev_name(device));
+}
+
+static void wave_out_tests(void)
+{
+    WAVEOUTCAPSA capsA;
+    WAVEOUTCAPSW capsW;
+    WAVEFORMATEX format;
+    HWAVEOUT wout;
+    MMRESULT rc;
+    UINT ndev,d;
+
+    ndev=waveOutGetNumDevs();
+    trace("found %d WaveOut devices\n",ndev);
+
+    rc=waveOutGetDevCapsA(ndev+1,&capsA,sizeof(capsA));
+    ok(rc==MMSYSERR_BADDEVICEID,
+       "waveOutGetDevCapsA(%s): MMSYSERR_BADDEVICEID expected, got %s\n",
+       dev_name(ndev+1),mmsys_error(rc));
+
+    rc=waveOutGetDevCapsW(ndev+1,&capsW,sizeof(capsW));
+    ok(rc==MMSYSERR_BADDEVICEID || rc==MMSYSERR_NOTSUPPORTED,
+       "waveOutGetDevCapsW(%s): MMSYSERR_BADDEVICEID or MMSYSERR_NOTSUPPORTED "
+       "expected, got %s\n",dev_name(ndev+1),mmsys_error(rc));
+
+    rc=waveOutGetDevCapsA(WAVE_MAPPER,&capsA,sizeof(capsA));
+    if (ndev>0)
+        ok(rc==MMSYSERR_NOERROR,
+           "waveOutGetDevCapsA(%s): MMSYSERR_NOERROR expected, got %s\n",
+           dev_name(WAVE_MAPPER),mmsys_error(rc));
+    else
+        ok(rc==MMSYSERR_BADDEVICEID || rc==MMSYSERR_NODRIVER,
+           "waveOutGetDevCapsA(%s): MMSYSERR_BADDEVICEID or MMSYSERR_NODRIVER "
+           "expected, got %s\n",dev_name(WAVE_MAPPER),mmsys_error(rc));
+
+    rc=waveOutGetDevCapsW(WAVE_MAPPER,&capsW,sizeof(capsW));
+    if (ndev>0)
+        ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NOTSUPPORTED,
+           "waveOutGetDevCapsW(%s): MMSYSERR_NOERROR or MMSYSERR_NOTSUPPORTED "
+           "expected, got %s\n",dev_name(WAVE_MAPPER),mmsys_error(rc));
+    else
+        ok(rc==MMSYSERR_BADDEVICEID || rc==MMSYSERR_NODRIVER ||
+           rc==MMSYSERR_NOTSUPPORTED,
+           "waveOutGetDevCapsW(%s): MMSYSERR_BADDEVICEID or MMSYSERR_NODRIVER "
+           " or MMSYSERR_NOTSUPPORTED expected, got %s\n",
+           dev_name(WAVE_MAPPER),mmsys_error(rc));
+
+    format.wFormatTag=WAVE_FORMAT_PCM;
+    format.nChannels=2;
+    format.wBitsPerSample=16;
+    format.nSamplesPerSec=44100;
+    format.nBlockAlign=format.nChannels*format.wBitsPerSample/8;
+    format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign;
+    format.cbSize=0;
+    rc=waveOutOpen(&wout,ndev+1,&format,0,0,CALLBACK_NULL);
+    ok(rc==MMSYSERR_BADDEVICEID,
+       "waveOutOpen(%s): MMSYSERR_BADDEVICEID expected, got %s\n",
+       dev_name(ndev+1),mmsys_error(rc));
+
+    for (d=0;d<ndev;d++)
+        wave_out_test_device(d);
+
+    if (ndev>0)
+        wave_out_test_device(WAVE_MAPPER);
+}
+
+START_TEST(wave)
+{
+    test_multiple_waveopens();
+    wave_out_tests();
+}
diff --git a/rostests/winetests/winmm/winmm.rbuild b/rostests/winetests/winmm/winmm.rbuild
new file mode 100644 (file)
index 0000000..07ea90a
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<module name="winmm_winetest" type="win32cui" installbase="bin" installname="winmm_winetest.exe" allowwarnings="true">
+       <include base="winmm_winetest">.</include>
+       <define name="__ROS_LONG64__" />
+       <library>dxguid</library>
+       <library>winmm</library>
+       <library>user32</library>
+       <library>kernel32</library>
+       <file>capture.c</file>
+       <file>mci.c</file>
+       <file>mixer.c</file>
+       <file>mmio.c</file>
+       <file>timer.c</file>
+       <file>wave.c</file>
+       <file>testlist.c</file>
+</module>
diff --git a/rostests/winetests/winmm/winmm_test.h b/rostests/winetests/winmm/winmm_test.h
new file mode 100644 (file)
index 0000000..177dc2e
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2002 Francois Gouget
+ *
+ * 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
+ */
+
+#ifndef WAVE_FORMAT_48M08
+#define WAVE_FORMAT_48M08      0x00001000    /* 48     kHz, Mono,   8-bit  */
+#define WAVE_FORMAT_48S08      0x00002000    /* 48     kHz, Stereo, 8-bit  */
+#define WAVE_FORMAT_48M16      0x00004000    /* 48     kHz, Mono,   16-bit */
+#define WAVE_FORMAT_48S16      0x00008000    /* 48     kHz, Stereo, 16-bit */
+#define WAVE_FORMAT_96M08      0x00010000    /* 96     kHz, Mono,   8-bit  */
+#define WAVE_FORMAT_96S08      0x00020000    /* 96     kHz, Stereo, 8-bit  */
+#define WAVE_FORMAT_96M16      0x00040000    /* 96     kHz, Mono,   16-bit */
+#define WAVE_FORMAT_96S16      0x00080000    /* 96     kHz, Stereo, 16-bit */
+#endif
+
+#ifndef DRV_QUERYDEVICEINTERFACE
+#define DRV_QUERYDEVICEINTERFACE     (DRV_RESERVED + 12)
+#endif
+#ifndef DRV_QUERYDEVICEINTERFACESIZE
+#define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13)
+#endif
+
+static const unsigned int win_formats[][4] = {
+    {0,                  8000,  8, 1},
+    {0,                  8000,  8, 2},
+    {0,                  8000, 16, 1},
+    {0,                  8000, 16, 2},
+    {WAVE_FORMAT_1M08,  11025,  8, 1},
+    {WAVE_FORMAT_1S08,  11025,  8, 2},
+    {WAVE_FORMAT_1M16,  11025, 16, 1},
+    {WAVE_FORMAT_1S16,  11025, 16, 2},
+    {0,                 12000,  8, 1},
+    {0,                 12000,  8, 2},
+    {0,                 12000, 16, 1},
+    {0,                 12000, 16, 2},
+    {0,                 16000,  8, 1},
+    {0,                 16000,  8, 2},
+    {0,                 16000, 16, 1},
+    {0,                 16000, 16, 2},
+    {WAVE_FORMAT_2M08,  22050,  8, 1},
+    {WAVE_FORMAT_2S08,  22050,  8, 2},
+    {WAVE_FORMAT_2M16,  22050, 16, 1},
+    {WAVE_FORMAT_2S16,  22050, 16, 2},
+    {WAVE_FORMAT_4M08,  44100,  8, 1},
+    {WAVE_FORMAT_4S08,  44100,  8, 2},
+    {WAVE_FORMAT_4M16,  44100, 16, 1},
+    {WAVE_FORMAT_4S16,  44100, 16, 2},
+    {WAVE_FORMAT_48M08, 48000,  8, 1},
+    {WAVE_FORMAT_48S08, 48000,  8, 2},
+    {WAVE_FORMAT_48M16, 48000, 16, 1},
+    {WAVE_FORMAT_48S16, 48000, 16, 2},
+    {WAVE_FORMAT_96M08, 96000,  8, 1},
+    {WAVE_FORMAT_96S08, 96000,  8, 2},
+    {WAVE_FORMAT_96M16, 96000, 16, 1},
+    {WAVE_FORMAT_96S16, 96000, 16, 2}
+};
+#define NB_WIN_FORMATS (sizeof(win_formats)/sizeof(*win_formats))
+
+extern const char* dev_name(int);
+extern const char* wave_open_flags(DWORD);
+extern const char* mmsys_error(MMRESULT);
+extern const char* wave_out_error(MMRESULT);
+extern const char* get_format_str(WORD format);
+extern const char* wave_time_format(UINT type);
+extern DWORD bytes_to_samples(DWORD bytes, LPWAVEFORMATEX pwfx);
+extern DWORD bytes_to_ms(DWORD bytes, LPWAVEFORMATEX pwfx);
+extern DWORD time_to_bytes(LPMMTIME mmtime, LPWAVEFORMATEX pwfx);