[WINMM_WINETEST] Sync with Wine Staging 1.7.37. CORE-9246
authorAmine Khaldi <amine.khaldi@reactos.org>
Fri, 20 Mar 2015 18:22:14 +0000 (18:22 +0000)
committerAmine Khaldi <amine.khaldi@reactos.org>
Fri, 20 Mar 2015 18:22:14 +0000 (18:22 +0000)
svn path=/trunk/; revision=66839

rostests/winetests/winmm/CMakeLists.txt
rostests/winetests/winmm/capture.c
rostests/winetests/winmm/joystick.c [new file with mode: 0644]
rostests/winetests/winmm/mci.c
rostests/winetests/winmm/mcicda.c [new file with mode: 0644]
rostests/winetests/winmm/midi.c [new file with mode: 0644]
rostests/winetests/winmm/mixer.c
rostests/winetests/winmm/mmio.c
rostests/winetests/winmm/testlist.c
rostests/winetests/winmm/timer.c
rostests/winetests/winmm/wave.c

index f762ea8..ddaeff7 100644 (file)
@@ -1,7 +1,10 @@
 
 list(APPEND SOURCE
     capture.c
+    joystick.c
     mci.c
+    mcicda.c
+    midi.c
     mixer.c
     mmio.c
     timer.c
@@ -11,7 +14,7 @@ list(APPEND SOURCE
 add_executable(winmm_winetest ${SOURCE})
 target_link_libraries(winmm_winetest dxguid)
 set_module_type(winmm_winetest win32cui)
-add_importlibs(winmm_winetest winmm user32 msvcrt kernel32)
+add_importlibs(winmm_winetest winmm user32 advapi32 msvcrt kernel32)
 add_cd_file(TARGET winmm_winetest DESTINATION reactos/bin FOR all)
 
 if(NOT MSVC)
index c46d35a..54c3793 100644 (file)
@@ -27,6 +27,7 @@
 #include "windef.h"
 #include "winbase.h"
 #include "winnls.h"
+#include "mmddk.h"
 #include "mmsystem.h"
 #define NOBITMAP
 #include "mmreg.h"
@@ -42,9 +43,9 @@ static const char * wave_in_error(MMRESULT error)
     static char long_msg[1100];
     MMRESULT rc;
 
-    rc = waveInGetErrorText(error, msg, sizeof(msg));
+    rc = waveInGetErrorTextA(error, msg, sizeof(msg));
     if (rc != MMSYSERR_NOERROR)
-        sprintf(long_msg, "waveInGetErrorText(%x) failed with error %x", error, rc);
+        sprintf(long_msg, "waveInGetErrorTextA(%x) failed with error %x", error, rc);
     else
         sprintf(long_msg, "%s(%s)", mmsys_error(error), msg);
     return long_msg;
@@ -54,14 +55,9 @@ 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,
@@ -131,22 +127,19 @@ static void check_position(int device, HWAVEIN win, DWORD bytes,
        dev_name(device));
 }
 
-static void wave_in_test_deviceIn(int device, LPWAVEFORMATEX pwfx, DWORD format, DWORD flags, LPWAVEINCAPS pcaps)
+static void wave_in_test_deviceIn(int device, WAVEFORMATEX *pwfx, DWORD format, DWORD flags,
+        WAVEINCAPSA *pcaps)
 {
     HWAVEIN win;
-    HANDLE hevent;
+    HANDLE hevent = CreateEventW(NULL, FALSE, FALSE, NULL);
     WAVEHDR frag;
     MMRESULT rc;
     DWORD res;
+    MMTIME mmt;
     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 */
@@ -159,13 +152,13 @@ static void wave_in_test_deviceIn(int device, LPWAVEFORMATEX pwfx, DWORD format,
        (!(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",
+       "waveInOpen(%s): format=%dx%2dx%d flags=%x(%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 "
+        trace(" Reason: The device lists this format as supported in its "
               "capabilities but opening it failed.\n");
     if ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
        !(pcaps->dwFormats & format))
@@ -229,6 +222,13 @@ static void wave_in_test_deviceIn(int device, LPWAVEFORMATEX pwfx, DWORD format,
            "frag.dwBytesRecorded=%d, should=%d\n",
            frag.dwBytesRecorded,pwfx->nAvgBytesPerSec);
 
+        mmt.wType = TIME_BYTES;
+        rc=waveInGetPosition(win, &mmt, sizeof(mmt));
+        ok(rc==MMSYSERR_NOERROR,"waveInGetPosition(%s): rc=%s\n",
+           dev_name(device),wave_in_error(rc));
+        ok(mmt.wType == TIME_BYTES, "doesn't support TIME_BYTES: %u\n", mmt.wType);
+        ok(mmt.u.cb == frag.dwBytesRecorded, "Got wrong position: %u\n", mmt.u.cb);
+
         /* stop playing on error */
         if (res!=WAIT_OBJECT_0) {
             rc=waveInStop(win);
@@ -261,7 +261,7 @@ static void wave_in_test_deviceIn(int device, LPWAVEFORMATEX pwfx, DWORD format,
            rc==MMSYSERR_ALLOCATED ||
            ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
             !(pcaps->dwFormats & format)),
-           "waveOutOpen(%s) format=%dx%2dx%d flags=%lx(%s) rc=%s\n",
+           "waveOutOpen(%s) format=%dx%2dx%d flags=%x(%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));
@@ -299,7 +299,7 @@ static void wave_in_test_device(UINT_PTR device)
 {
     WAVEINCAPSA capsA;
     WAVEINCAPSW capsW;
-    WAVEFORMATEX format,oformat;
+    WAVEFORMATEX format;
     WAVEFORMATEXTENSIBLE wfex;
     HWAVEIN win;
     MMRESULT rc;
@@ -444,29 +444,6 @@ static void wave_in_test_device(UINT_PTR device)
         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;
@@ -477,7 +454,8 @@ static void wave_in_test_device(UINT_PTR device)
     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,
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM ||
+       rc==MMSYSERR_ALLOCATED,
        "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
     if (rc==MMSYSERR_NOERROR) {
         waveInClose(win);
@@ -495,7 +473,8 @@ static void wave_in_test_device(UINT_PTR device)
     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,
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM ||
+       rc==MMSYSERR_ALLOCATED,
        "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
     if (rc==MMSYSERR_NOERROR) {
         waveInClose(win);
@@ -519,7 +498,8 @@ static void wave_in_test_device(UINT_PTR device)
     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,
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM ||
+       rc==MMSYSERR_ALLOCATED,
        "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
     if (rc==MMSYSERR_NOERROR) {
         waveInClose(win);
@@ -543,7 +523,8 @@ static void wave_in_test_device(UINT_PTR device)
     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,
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM ||
+       rc==MMSYSERR_ALLOCATED,
        "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
     if (rc==MMSYSERR_NOERROR) {
         waveInClose(win);
@@ -567,7 +548,8 @@ static void wave_in_test_device(UINT_PTR device)
     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,
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM ||
+       rc==MMSYSERR_ALLOCATED,
        "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
     if (rc==MMSYSERR_NOERROR) {
         waveInClose(win);
@@ -594,7 +576,8 @@ static void wave_in_test_device(UINT_PTR device)
     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,
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM ||
+       rc==MMSYSERR_ALLOCATED,
        "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
     if (rc==MMSYSERR_NOERROR) {
         waveInClose(win);
@@ -619,7 +602,8 @@ static void wave_in_test_device(UINT_PTR device)
     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,
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM ||
+       rc==MMSYSERR_ALLOCATED,
        "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
     if (rc==MMSYSERR_NOERROR) {
         waveInClose(win);
@@ -643,7 +627,8 @@ static void wave_in_test_device(UINT_PTR device)
     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,
+       rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM ||
+       rc==MMSYSERR_ALLOCATED,
        "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc));
     if (rc==MMSYSERR_NOERROR) {
         waveInClose(win);
@@ -660,25 +645,30 @@ static void wave_in_tests(void)
     WAVEFORMATEX format;
     HWAVEIN win;
     MMRESULT rc;
+    DWORD preferred, status;
     UINT ndev,d;
 
     ndev=waveInGetNumDevs();
     trace("found %d WaveIn devices\n",ndev);
 
+    rc = waveInMessage((HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
+            (DWORD_PTR)&preferred, (DWORD_PTR)&status);
+    ok((ndev == 0 && (rc == MMSYSERR_NODRIVER || rc == MMSYSERR_BADDEVICEID)) ||
+            rc == MMSYSERR_NOTSUPPORTED ||
+            rc == MMSYSERR_NOERROR, "waveInMessage(DRVM_MAPPER_PREFERRED_GET) failed: %u\n", rc);
+
+    if(rc != MMSYSERR_NOTSUPPORTED)
+        ok((ndev == 0 && (preferred == -1 || broken(preferred != -1))) ||
+                preferred < ndev, "Got invalid preferred device: 0x%x\n", preferred);
+
     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));
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NODRIVER || (!ndev && (rc==MMSYSERR_BADDEVICEID)),
+       "waveInGetDevCapsA(%s): 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,
@@ -686,18 +676,9 @@ static void wave_in_tests(void)
        "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));
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NODRIVER ||
+       rc==MMSYSERR_NOTSUPPORTED || (!ndev && (rc==MMSYSERR_BADDEVICEID)),
+       "waveInGetDevCapsW(%s): got %s\n", dev_name(ndev+1),wave_in_error(rc));
 
     format.wFormatTag=WAVE_FORMAT_PCM;
     format.nChannels=2;
diff --git a/rostests/winetests/winmm/joystick.c b/rostests/winetests/winmm/joystick.c
new file mode 100644 (file)
index 0000000..21e90e3
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Unit tests for joystick APIs
+ *
+ * Copyright 2014 Bruno Jesus
+ *
+ * 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 "windef.h"
+#include "winbase.h"
+#include "winuser.h"
+#include "mmsystem.h"
+#include "wine/test.h"
+
+static HWND window;
+
+static LRESULT CALLBACK proc_window(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+    return DefWindowProcA(hwnd, msg, wparam, lparam);
+}
+
+static void create_window(void)
+{
+    const char name[]  = "Joystick Test";
+    WNDCLASSA wc;
+
+    memset(&wc, 0, sizeof(wc));
+    wc.lpfnWndProc   = proc_window;
+    wc.hInstance     = 0;
+    wc.lpszClassName = name;
+    RegisterClassA(&wc);
+    window = CreateWindowExA(0, name, name, WS_OVERLAPPEDWINDOW,
+                             CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+                             NULL, NULL, NULL, NULL);
+    ok(window != NULL, "Expected CreateWindowEx to work, error %d\n", GetLastError());
+}
+
+static void destroy_window(void)
+{
+    DestroyWindow(window);
+    window = NULL;
+}
+
+static void test_api(void)
+{
+    MMRESULT ret;
+    JOYCAPSA jc;
+    JOYCAPSW jcw;
+    JOYINFO info;
+    union _infoex
+    {
+        JOYINFOEX ex;
+        char buffer[sizeof(JOYINFOEX) * 2];
+    } infoex;
+    UINT i, par, devices, joyid, win98 = 0, win8 = 0;
+    UINT period[] = {0, 1, 9, 10, 100, 1000, 1001, 10000, 65535, 65536, 0xFFFFFFFF};
+    UINT threshold_error = 0x600, period_win8_error = 0x7CE;
+    UINT flags[] = { JOY_RETURNALL, JOY_RETURNBUTTONS, JOY_RETURNCENTERED, JOY_RETURNPOV,
+                     JOY_RETURNPOVCTS, JOY_RETURNR, JOY_RETURNRAWDATA, JOY_RETURNU,
+                     JOY_RETURNV, JOY_RETURNX, JOY_RETURNY, JOY_RETURNZ };
+
+    devices = joyGetNumDevs();
+    joyid = -1;
+    /* joyGetNumDevs does NOT return the number of joysticks connected, only slots in the OS */
+    for (i = 0; i < devices; i++)
+    {
+        memset(&jc, 0, sizeof(jc));
+        ret = joyGetDevCapsA(JOYSTICKID1 + i, &jc, sizeof(jc));
+        if (ret == JOYERR_NOERROR)
+        {
+            joyid = JOYSTICKID1 + i;
+            trace("Joystick[%d] - name: '%s', axes: %d, buttons: %d, period range: %d - %d\n",
+                  JOYSTICKID1 + i, jc.szPname, jc.wNumAxes, jc.wNumButtons, jc.wPeriodMin, jc.wPeriodMax);
+            ret = joyGetDevCapsW(JOYSTICKID1 + i, &jcw, sizeof(jcw));
+            if (ret != MMSYSERR_NOTSUPPORTED) /* Win 98 */
+            {
+                ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret);
+                ok(jc.wNumAxes == jcw.wNumAxes, "Expected %d == %d\n", jc.wNumAxes, jcw.wNumAxes);
+                ok(jc.wNumButtons == jcw.wNumButtons, "Expected %d == %d\n", jc.wNumButtons, jcw.wNumButtons);
+            }
+            else win98++;
+            break;
+        }
+        else
+        {
+            ok(ret == JOYERR_PARMS, "Expected %d, got %d\n", JOYERR_PARMS, ret);
+            ret = joyGetDevCapsW(JOYSTICKID1 + i, &jcw, sizeof(jcw));
+            ok(ret == JOYERR_PARMS || (ret == MMSYSERR_NOTSUPPORTED) /* Win 98 */,
+               "Expected %d, got %d\n", JOYERR_PARMS, ret);
+        }
+    }
+    /* Test invalid joystick - If no joystick is present the driver is not initialized,
+     * so a NODRIVER error is returned, if at least one joystick is present the error is
+     * about invalid parameters. */
+    ret = joyGetDevCapsA(joyid + devices, &jc, sizeof(jc));
+    ok(ret == MMSYSERR_NODRIVER || ret == JOYERR_PARMS,
+       "Expected %d or %d, got %d\n", MMSYSERR_NODRIVER, JOYERR_PARMS, ret);
+
+    if (joyid == -1)
+    {
+        skip("This test requires a real joystick.\n");
+        return;
+    }
+
+    /* Capture tests */
+    ret = joySetCapture(NULL, joyid, 100, FALSE);
+    ok(ret == JOYERR_PARMS || broken(win98 && ret == MMSYSERR_INVALPARAM) /* Win 98 */,
+       "Expected %d, got %d\n", JOYERR_PARMS, ret);
+    ret = joySetCapture(window, joyid, 100, FALSE);
+    ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret);
+    ret = joySetCapture(window, joyid, 100, FALSE); /* double capture */
+    if (ret == JOYERR_NOCANDO)
+    {
+        todo_wine
+        ok(broken(1), "Expected double capture using joySetCapture to work\n");
+        if (!win98 && broken(1)) win8++; /* Windows 98 or 8 cannot cope with that */
+    }
+    else ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret);
+    ret = joyReleaseCapture(joyid);
+    ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret);
+    ret = joyReleaseCapture(joyid);
+    ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret); /* double release */
+
+    /* Try some unusual period values for joySetCapture and unusual threshold values for joySetThreshold.
+     * Windows XP allows almost all test values, Windows 8 will return error on most test values, Windows
+     * 98 allows anything but cuts the values to their maximum supported values internally. */
+    for (i = 0; i < sizeof(period) / sizeof(period[0]); i++)
+    {
+        ret = joySetCapture(window, joyid, period[i], FALSE);
+        if (win8 && ((1 << i) & period_win8_error))
+            ok(ret == JOYERR_NOCANDO, "Test [%d]: Expected %d, got %d\n", i, JOYERR_NOCANDO, ret);
+        else
+            ok(ret == JOYERR_NOERROR, "Test [%d]: Expected %d, got %d\n", i, JOYERR_NOERROR, ret);
+        ret = joyReleaseCapture(joyid);
+        ok(ret == JOYERR_NOERROR, "Test [%d]: Expected %d, got %d\n", i, JOYERR_NOERROR, ret);
+        /* Reuse the periods to test the threshold */
+        ret = joySetThreshold(joyid, period[i]);
+        if (!win98 && (1 << i) & threshold_error)
+            ok(ret == MMSYSERR_INVALPARAM, "Test [%d]: Expected %d, got %d\n", i, MMSYSERR_INVALPARAM, ret);
+        else
+            ok(ret == JOYERR_NOERROR, "Test [%d]: Expected %d, got %d\n", i, JOYERR_NOERROR, ret);
+        par = 0xdead;
+        ret = joyGetThreshold(joyid, &par);
+        ok(ret == JOYERR_NOERROR, "Test [%d]: Expected %d, got %d\n", i, JOYERR_NOERROR, ret);
+        if (!win98 || (win98 && i < 8))
+        {
+            if ((1 << i) & threshold_error)
+                ok(par == period[8], "Test [%d]: Expected %d, got %d\n", i, period[8], par);
+            else
+                ok(par == period[i], "Test [%d]: Expected %d, got %d\n", i, period[i], par);
+        }
+    }
+
+    /* Position tests */
+    ret = joyGetPos(joyid, NULL);
+    ok(ret == MMSYSERR_INVALPARAM, "Expected %d, got %d\n", MMSYSERR_INVALPARAM, ret);
+    ret = joyGetPos(joyid, &info);
+    ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret);
+    ret = joyGetPosEx(joyid, NULL);
+    ok(ret == MMSYSERR_INVALPARAM || broken(win8 && ret == JOYERR_PARMS) /* Win 8 */,
+       "Expected %d, got %d\n", MMSYSERR_INVALPARAM, ret);
+    memset(&infoex, 0, sizeof(infoex));
+    ret = joyGetPosEx(joyid, &infoex.ex);
+    ok(ret == JOYERR_PARMS || broken(win98 && ret == MMSYSERR_INVALPARAM),
+       "Expected %d, got %d\n", JOYERR_PARMS, ret);
+    infoex.ex.dwSize = sizeof(infoex.ex);
+    ret = joyGetPosEx(joyid, &infoex.ex);
+    ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret);
+    infoex.ex.dwSize = sizeof(infoex.ex) - 1;
+    ret = joyGetPosEx(joyid, &infoex.ex);
+    ok(ret == JOYERR_PARMS || broken(win98 && ret == MMSYSERR_INVALPARAM),
+       "Expected %d, got %d\n", JOYERR_PARMS, ret);
+    infoex.ex.dwSize = sizeof(infoex);
+    ret = joyGetPosEx(joyid, &infoex.ex);
+    ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret);
+
+    infoex.ex.dwSize = sizeof(infoex.ex);
+    for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++)
+    {
+        infoex.ex.dwFlags = flags[i];
+        ret = joyGetPosEx(joyid, &infoex.ex);
+        ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret);
+    }
+}
+
+START_TEST(joystick)
+{
+    create_window();
+    test_api();
+    destroy_window();
+}
index 6607e17..a729b5f 100644 (file)
 static MCIERROR ok_saved = MCIERR_FILE_NOT_FOUND;
 
 typedef union {
+      MCI_INFO_PARMSA     info;
       MCI_STATUS_PARMS    status;
       MCI_WAVE_SET_PARMS  set;
-      MCI_WAVE_OPEN_PARMS open;
+      MCI_WAVE_OPEN_PARMSA open;
+      MCI_GETDEVCAPS_PARMS caps;
+      MCI_SYSINFO_PARMSA  sys;
       MCI_SEEK_PARMS      seek;
+      MCI_GENERIC_PARMS   gen;
     } MCI_PARMS_UNION;
 
-static const char* dbg_mcierr(MCIERROR err)
+const char* dbg_mcierr(MCIERROR err)
 {
      switch (err) {
      case 0: return "0=NOERROR";
@@ -135,65 +139,289 @@ static BOOL spurious_message(LPMSG msg)
   return FALSE;
 }
 
-static void test_notification(HWND hwnd, const char* command, WPARAM type)
+/* A single ok() in each code path allows us to prefix this with todo_wine */
+#define test_notification(hwnd, command, type) test_notification_dbg(hwnd, command, type, __LINE__)
+static void test_notification_dbg(HWND hwnd, const char* command, WPARAM type, int line)
 {   /* Use type 0 as meaning no message */
     MSG msg;
     BOOL seen;
     do { seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE); }
     while(seen && spurious_message(&msg));
-    if(type==0)
-        ok(!seen, "Expect no message from command %s\n", command);
-    else
-        ok(seen, "PeekMessage should succeed for command %s\n", command);
-    if(seen) {
-        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 from command %s\n", msg.message, command);
-        ok(msg.wParam == type, "got %04lx instead of MCI_NOTIFY_xyz %04lx from command %s\n", msg.wParam, type, command);
+    if(type && !seen) {
+      /* We observe transient delayed notification, mostly on native.
+       * Notification is not always present right when mciSend returns. */
+      trace_(__FILE__,line)("Waiting for delayed notification from %s\n", command);
+      MsgWaitForMultipleObjects(0, NULL, FALSE, 3000, QS_POSTMESSAGE);
+      seen = PeekMessageA(&msg, hwnd, MM_MCINOTIFY, MM_MCINOTIFY, PM_REMOVE);
     }
+    if(!seen)
+      ok_(__FILE__,line)(type==0, "Expect message %04lx from %s\n", type, command);
+    else if(msg.hwnd != hwnd)
+        ok_(__FILE__,line)(msg.hwnd == hwnd, "Didn't get the handle to our test window\n");
+    else if(msg.message != MM_MCINOTIFY)
+        ok_(__FILE__,line)(msg.message == MM_MCINOTIFY, "got %04x instead of MM_MCINOTIFY from command %s\n", msg.message, command);
+    else ok_(__FILE__,line)(msg.wParam == type, "got %04lx instead of MCI_NOTIFY_xyz %04lx from command %s\n", msg.wParam, type, command);
 }
-static void test_notification1(HWND hwnd, const char* command, WPARAM type)
-{   /* This version works with todo_wine prefix. */
-    MSG msg;
-    BOOL seen;
-    do { seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE); }
-    while(seen && spurious_message(&msg));
-    if(type==0)
-        ok(!seen, "Expect no message from command %s\n", command);
-    else if(seen)
-      ok(msg.message == MM_MCINOTIFY && msg.wParam == type,"got %04lx instead of MCI_NOTIFY_xyz %04lx from command %s\n", msg.wParam, type, command);
-    else ok(seen, "PeekMessage should succeed for command %s\n", command);
+
+static int strcmp_wa(LPCWSTR strw, const char *stra)
+{
+    CHAR buf[512];
+    WideCharToMultiByte(CP_ACP, 0, strw, -1, buf, sizeof(buf), 0, 0);
+    return lstrcmpA(buf, stra);
+}
+
+static void test_mciParser(HWND hwnd)
+{
+    MCIERROR err;
+    MCIDEVICEID wDeviceID;
+    MCI_PARMS_UNION parm;
+    char buf[1024];
+    memset(buf, 0, sizeof(buf));
+    test_notification(hwnd, "-prior to parser test-", 0);
+
+    /* Get a handle on an MCI device, works even without sound. */
+    parm.open.lpstrDeviceType = "waveaudio";
+    parm.open.lpstrElementName = ""; /* "new" at the command level */
+    parm.open.lpstrAlias = "x"; /* to enable mciSendStringA */
+    parm.open.dwCallback = (DWORD_PTR)hwnd;
+    err = mciSendCommandA(0, MCI_OPEN,
+            MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_ALIAS | MCI_NOTIFY, (DWORD_PTR)&parm);
+    ok(!err,"mciCommand open new type waveaudio alias x notify: %s\n", dbg_mcierr(err));
+    wDeviceID = parm.open.wDeviceID;
+    ok(!strcmp(parm.open.lpstrDeviceType,"waveaudio"), "open modified device type\n");
+
+    test_notification(hwnd, "MCI_OPEN", MCI_NOTIFY_SUCCESSFUL);
+    test_notification(hwnd, "MCI_OPEN no #2", 0);
+
+    err = mciSendStringA("open avivideo alias a", buf, sizeof(buf), hwnd);
+    ok(!err,"open another: %s\n", dbg_mcierr(err));
+
+    buf[0]='z';
+    err = mciSendStringA("", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_MISSING_COMMAND_STRING,"empty string: %s\n", dbg_mcierr(err));
+    ok(!buf[0], "error buffer %s\n", buf);
+
+    buf[0]='d';
+    err = mciSendStringA("open", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_MISSING_DEVICE_NAME,"open void: %s\n", dbg_mcierr(err));
+    ok(!buf[0], "open error buffer %s\n", buf);
+
+    err = mciSendStringA("open notify", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_INVALID_DEVICE_NAME,"open notify: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("open new", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_NEW_REQUIRES_ALIAS,"open new: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("open new type waveaudio alias r shareable shareable", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_DUPLICATE_FLAGS,"open new: %s\n", dbg_mcierr(err));
+    if(!err) mciSendStringA("close r", NULL, 0, NULL);
+
+    err = mciSendStringA("status x position wait wait", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_DUPLICATE_FLAGS,"status wait wait: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status x length length", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_FLAGS_NOT_COMPATIBLE,"status 2xlength: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status x length position", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_FLAGS_NOT_COMPATIBLE,"status length+position: %s\n", dbg_mcierr(err));
+
+    buf[0]='I';
+    err = mciSendStringA("set x time format milliseconds time format ms", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_FLAGS_NOT_COMPATIBLE,"status length+position: %s\n", dbg_mcierr(err));
+    ok(!buf[0], "set error buffer %s\n", buf);
+
+    /* device's response, not a parser test */
+    err = mciSendStringA("status x", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_MISSING_PARAMETER,"status waveaudio nokeyword: %s\n", dbg_mcierr(err));
+
+    buf[0]='G';
+    err = mciSendStringA("status a", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_UNSUPPORTED_FUNCTION,"status avivideo nokeyword: %s\n", dbg_mcierr(err));
+    ok(!buf[0], "status error buffer %s\n", buf);
+
+    err = mciSendStringA("status x track", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_BAD_INTEGER,"status waveaudio no track: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status x track 3", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_MISSING_PARAMETER,"status waveaudio track 3: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status x 2 track 3", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_OUTOFRANGE,"status 2(position) track 3: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status x 0x4", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_BAD_CONSTANT, "status 0x4: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status x 4", buf, sizeof(buf), hwnd);
+    ok(!err,"status 4(mode): %s\n", dbg_mcierr(err));
+    if(!err)ok(!strcmp(buf,"stopped"), "status 4(mode), got: %s\n", buf);
+
+    err = mciSendStringA("status x 4 notify", buf, sizeof(buf), hwnd);
+    todo_wine ok(!err,"status 4(mode) notify: %s\n", dbg_mcierr(err));
+    if(!err)ok(!strcmp(buf,"stopped"), "status 4(mode), got: %s\n", buf);
+    test_notification(hwnd, "status 4 notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
+
+    err = mciSendStringA("set x milliseconds", buf, sizeof(buf), hwnd);
+    todo_wine ok(err==MCIERR_UNRECOGNIZED_KEYWORD,"set milliseconds: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("set x milliseconds ms", buf, sizeof(buf), hwnd);
+    todo_wine ok(err==MCIERR_UNRECOGNIZED_KEYWORD,"set milliseconds ms: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("capability x can   save", buf, sizeof(buf), hwnd);
+    todo_wine ok(!err,"capability can (space) save: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status x nsa", buf, sizeof(buf), hwnd);
+    todo_wine ok(err==MCIERR_BAD_CONSTANT,"status nsa: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("seek x to 0:0:0:0:0", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_BAD_INTEGER,"seek to 0:0:0:0:0 returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("seek x to 0:0:0:0:", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_BAD_INTEGER,"seek to 0:0:0:0: returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("seek x to :0:0:0:0", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_BAD_INTEGER,"seek to :0:0:0:0 returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("seek x to 256:0:0:0", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_BAD_INTEGER,"seek to 256:0:0:0 returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("seek x to 0:256", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_BAD_INTEGER,"seek to 0:256 returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status all time format", buf, sizeof(buf), hwnd);
+    ok(err==MCIERR_CANNOT_USE_ALL,"status all: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("cue all", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_UNRECOGNIZED_COMMAND,"cue all: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("open all", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_CANNOT_USE_ALL,"open all: %s\n", dbg_mcierr(err));
+
+    /* avivideo is not a known MCI_DEVTYPE resource name */
+    err = mciSendStringA("sysinfo avivideo quantity", buf, sizeof(buf), hwnd);
+    ok(err==MCIERR_DEVICE_TYPE_REQUIRED,"sysinfo sequencer quantity: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("sysinfo digitalvideo quantity", buf, sizeof(buf), hwnd);
+    ok(!err,"sysinfo digitalvideo quantity: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf,"0"), "sysinfo digitalvideo quantity returned %s\n", buf);
+
+    /* quantity 0 yet open 1 (via type "avivideo"), fun */
+    err = mciSendStringA("sysinfo digitalvideo quantity open", buf, sizeof(buf), hwnd);
+    ok(!err,"sysinfo digitalvideo quantity open: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf,"1"), "sysinfo digitalvideo quantity open returned %s\n", buf);
+
+    err = mciSendStringA("put a window at 0 0", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_BAD_INTEGER,"put incomplete rect: %s\n", dbg_mcierr(err));
+
+    /*w9X-w2k report code from device last opened, newer versions compare them all
+     * and return the one error code or MCIERR_MULTIPLE if they differ. */
+    err = mciSendStringA("pause all", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_MULTIPLE || broken(err==MCIERR_NONAPPLICABLE_FUNCTION),"pause all: %s\n", dbg_mcierr(err));
+    ok(!buf[0], "pause error buffer %s\n", buf);
+
+    /* MCI_STATUS' dwReturn is a DWORD_PTR, others' a plain DWORD. */
+    parm.status.dwItem = MCI_STATUS_TIME_FORMAT;
+    parm.status.dwReturn = 0xFEEDABAD;
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
+    ok(!err,"mciCommand status time format: %s\n", dbg_mcierr(err));
+    if(!err) ok(MCI_FORMAT_MILLISECONDS==parm.status.dwReturn,"status time format: %ld\n",parm.status.dwReturn);
+
+    parm.status.dwItem = MCI_STATUS_MODE;
+    parm.status.dwReturn = 0xFEEDABAD;
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
+    ok(!err,"mciCommand status mode: %s\n", dbg_mcierr(err));
+    if(!err) ok(MCI_MODE_STOP==parm.status.dwReturn,"STATUS mode: %ld\n",parm.status.dwReturn);
+
+    err = mciSendStringA("status x mode", buf, sizeof(buf), hwnd);
+    ok(!err,"status mode: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "stopped"), "status mode is %s\n", buf);
+
+    parm.caps.dwItem = MCI_GETDEVCAPS_USES_FILES;
+    parm.caps.dwReturn = 0xFEEDABAD;
+    err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
+    ok(!err,"mciCommand getdevcaps files: %s\n", dbg_mcierr(err));
+    if(!err) ok(1==parm.caps.dwReturn,"getdevcaps files: %d\n",parm.caps.dwReturn);
+
+    parm.caps.dwItem = MCI_GETDEVCAPS_HAS_VIDEO;
+    parm.caps.dwReturn = 0xFEEDABAD;
+    err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
+    ok(!err,"mciCommand getdevcaps video: %s\n", dbg_mcierr(err));
+    if(!err) ok(0==parm.caps.dwReturn,"getdevcaps video: %d\n",parm.caps.dwReturn);
+
+    parm.caps.dwItem = MCI_GETDEVCAPS_DEVICE_TYPE;
+    parm.caps.dwReturn = 0xFEEDABAD;
+    err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
+    ok(!err,"mciCommand getdevcaps video: %s\n", dbg_mcierr(err));
+    if(!err) ok(MCI_DEVTYPE_WAVEFORM_AUDIO==parm.caps.dwReturn,"getdevcaps device type: %d\n",parm.caps.dwReturn);
+
+    err = mciSendStringA("capability x uses files", buf, sizeof(buf), hwnd);
+    ok(!err,"capability files: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "true"), "capability files is %s\n", buf);
+
+    err = mciSendStringA("capability x has video", buf, sizeof(buf), hwnd);
+    ok(!err,"capability video: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "false"), "capability video is %s\n", buf);
+
+    err = mciSendStringA("capability x device type", buf, sizeof(buf), hwnd);
+    ok(!err,"capability device type: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "waveaudio"), "capability device type is %s\n", buf);
+
+    err = mciSendCommandA(wDeviceID, MCI_CLOSE, 0, 0);
+    ok(!err,"mciCommand close returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("close a", buf, sizeof(buf), hwnd);
+    ok(!err,"close avi: %s\n", dbg_mcierr(err));
+
+    test_notification(hwnd, "-end of 1st set-", 0);
 }
 
 static void test_openCloseWAVE(HWND hwnd)
 {
     MCIERROR err;
-    MCI_GENERIC_PARMS parm;
+    MCI_PARMS_UNION parm;
     const char command_open[] = "open new type waveaudio alias mysound notify";
     const char command_close_my[] = "close mysound notify";
     const char command_close_all[] = "close all notify";
     const char command_sysinfo[] = "sysinfo waveaudio quantity open";
     char buf[1024];
+    DWORD intbuf[3] = { 0xDEADF00D, 99, 0xABADCAFE };
     memset(buf, 0, sizeof(buf));
     test_notification(hwnd, "-prior to any command-", 0);
 
-    err = mciSendString("sysinfo all quantity", buf, sizeof(buf), hwnd);
-    todo_wine ok(!err,"mci %s returned %s\n", command_open, dbg_mcierr(err));
+    /* Avoid Sysinfo quantity with notify because Win9x and newer differ. */
+    err = mciSendStringA("sysinfo all quantity", buf, sizeof(buf), hwnd);
+    ok(!err,"mci sysinfo all quantity returned %s\n", dbg_mcierr(err));
     if(!err) trace("[MCI] with %s drivers\n", buf);
 
-    err = mciSendString("open new type waveaudio alias r shareable", buf, sizeof(buf), NULL);
+    parm.sys.lpstrReturn = (LPSTR)&intbuf[1];
+    parm.sys.dwRetSize = sizeof(DWORD);
+    parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */
+    err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_QUANTITY | MCI_WAIT,
+            (DWORD_PTR)&parm);
+    ok(!err, "mciCommand sysinfo all quantity returned %s\n", dbg_mcierr(err));
+    if(!err) ok(atoi(buf)==intbuf[1],"sysinfo all quantity string and command differ\n");
+
+    parm.sys.dwRetSize = sizeof(DWORD)-1;
+    err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_QUANTITY, (DWORD_PTR)&parm);
+    ok(err == MCIERR_PARAM_OVERFLOW || broken(!err/* Win9x */),
+            "mciCommand sysinfo with too small buffer returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("open new type waveaudio alias r shareable", buf, sizeof(buf), NULL);
     ok(err==MCIERR_UNSUPPORTED_FUNCTION,"mci open new shareable returned %s\n", dbg_mcierr(err));
     if(!err) {
-        err = mciSendString("close r", NULL, 0, NULL);
+        err = mciSendStringA("close r", NULL, 0, NULL);
         ok(!err,"mci close shareable returned %s\n", dbg_mcierr(err));
     }
 
-    err = mciSendString(command_open, buf, sizeof(buf), hwnd);
+    err = mciGetDeviceIDA("waveaudio");
+    ok(!err, "mciGetDeviceIDA waveaudio returned %u, expected 0\n", err);
+
+    err = mciSendStringA(command_open, buf, sizeof(buf), hwnd);
     ok(!err,"mci %s returned %s\n", command_open, dbg_mcierr(err));
     ok(!strcmp(buf,"1"), "mci open deviceId: %s, expected 1\n", buf);
     /* Wine<=1.1.33 used to ignore anything past alias XY */
     test_notification(hwnd,"open new alias notify",MCI_NOTIFY_SUCCESSFUL);
 
-    err = mciSendString("status mysound time format", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status mysound time format", buf, sizeof(buf), hwnd);
     ok(!err,"mci status time format returned %s\n", dbg_mcierr(err));
     if(!err) {
         if (PRIMARYLANGID(LANGIDFROMLCID(GetThreadLocale())) == LANG_ENGLISH)
@@ -201,54 +429,190 @@ static void test_openCloseWAVE(HWND hwnd)
         else trace("locale-dependent time format: %s (ms)\n", buf);
     }
 
-    err = mciSendString(command_close_my, NULL, 0, hwnd);
+    memset(buf, 0, sizeof(buf));
+    parm.sys.dwNumber = 1;
+    parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */
+    parm.sys.lpstrReturn = buf;
+    parm.sys.dwRetSize = sizeof(buf);
+    parm.sys.dwCallback = (DWORD_PTR)hwnd;
+    err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO,
+            MCI_SYSINFO_NAME | MCI_SYSINFO_OPEN | MCI_NOTIFY, (DWORD_PTR)&parm);
+    ok(!err,"mciCommand MCI_SYSINFO all name 1 open notify: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf,"mysound"), "sysinfo name returned %s\n", buf);
+    test_notification(hwnd, "SYSINFO name notify\n", MCI_NOTIFY_SUCCESSFUL);
+
+    memset(buf, 0, sizeof(buf));
+    parm.sys.dwNumber = 1;
+    parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */
+    parm.sys.lpstrReturn = buf;
+    parm.sys.dwRetSize = 8; /* mysound\0 */
+    err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_NAME | MCI_SYSINFO_OPEN,
+            (DWORD_PTR)&parm);
+    ok(!err,"mciCommand MCI_SYSINFO all name 1 open buffer[8]: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf,"mysound"), "sysinfo name returned %s\n", buf);
+
+    memset(buf, 0, sizeof(buf));
+    /* dwRetSize counts characters, not bytes, despite what MSDN says. */
+    parm.sys.dwNumber = 1;
+    parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */
+    parm.sys.lpstrReturn = buf;
+    parm.sys.dwRetSize = 8; /* mysound\0 */
+    /* MCI_..._PARMSA and PARMSW share the same layout, use one for both tests. */
+    err = mciSendCommandW(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_NAME | MCI_SYSINFO_OPEN, (DWORD_PTR)&parm);
+    ok(!err || broken(err==MMSYSERR_NOTSUPPORTED/* Win9x */), "mciCommandW MCI_SYSINFO all name 1 open buffer[8]: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp_wa((LPWSTR)buf,"mysound"), "sysinfo name 1 open contents\n");
+
+    memset(buf, 0, sizeof(buf));
+    buf[0] = 'Y';
+    parm.sys.dwNumber = 1;
+    parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */
+    parm.sys.lpstrReturn = buf;
+    parm.sys.dwRetSize = 7; /* too short for mysound\0 */
+    err = mciSendCommandW(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_NAME | MCI_SYSINFO_OPEN, (DWORD_PTR)&parm);
+    ok(err==MCIERR_PARAM_OVERFLOW || broken(err==MMSYSERR_NOTSUPPORTED/* Win9x */), "mciCommandW MCI_SYSINFO all name 1 open too small: %s\n", dbg_mcierr(err));
+    ok(!strcmp(buf,"Y"), "output buffer %s\n", buf);
+
+    /* Win9x overwrites the tiny buffer and returns success, newer versions signal overflow. */
+    memset(buf, 0, sizeof(buf));
+    buf[0] = 'Y';
+    parm.sys.dwNumber = 1;
+    parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */
+    parm.sys.lpstrReturn = buf;
+    parm.sys.dwRetSize = 2; /* too short for mysound\0 */
+    err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_NAME | MCI_SYSINFO_OPEN,
+            (DWORD_PTR)&parm);
+    ok(err==MCIERR_PARAM_OVERFLOW || broken(!err /* Win9x */),"mciCommand MCI_SYSINFO all name 1 open too small: %s\n", dbg_mcierr(err));
+    ok(!strcmp(buf, err ? "Y" : "mysound"), "sysinfo short name returned %s\n", buf);
+
+    err = mciSendStringA("sysinfo mysound quantity open", buf, sizeof(buf), hwnd);
+    ok(err==MCIERR_DEVICE_TYPE_REQUIRED,"sysinfo alias quantity: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("sysinfo nosuchalias quantity open", buf, sizeof(buf), hwnd);
+    ok(err==MCIERR_DEVICE_TYPE_REQUIRED,"sysinfo unknown quantity open: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("sysinfo all installname", buf, sizeof(buf), hwnd);
+    ok(err==MCIERR_CANNOT_USE_ALL,"sysinfo all installname: %s\n", dbg_mcierr(err));
+
+    buf[0] = 'M'; buf[1] = 0;
+    parm.sys.lpstrReturn = buf;
+    parm.sys.dwRetSize = sizeof(buf);
+    err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_INSTALLNAME, (DWORD_PTR)&parm);
+    ok(err==MCIERR_CANNOT_USE_ALL,"mciCommand MCI_SYSINFO all installname: %s\n", dbg_mcierr(err));
+    ok(!strcmp(buf,"M"), "output buffer %s\n", buf);
+
+    err = mciSendStringA("sysinfo nodev installname", buf, sizeof(buf), hwnd);
+    ok(err==MCIERR_INVALID_DEVICE_NAME,"sysinfo nodev installname: %s\n", dbg_mcierr(err));
+    ok(!buf[0], "sysinfo error buffer %s\n", buf);
+
+    buf[0] = 'K';
+    parm.sys.lpstrReturn = buf;
+    parm.sys.dwRetSize = sizeof(buf);
+    err = mciSendCommandW(24000, MCI_SYSINFO, MCI_SYSINFO_INSTALLNAME, (DWORD_PTR)&parm);
+    ok(err==MCIERR_INVALID_DEVICE_NAME || broken(err==MMSYSERR_NOTSUPPORTED/* Win9x */), "mciCommand MCI_SYSINFO nodev installname: %s\n", dbg_mcierr(err));
+    ok(!strcmp(buf,"K"), "output buffer %s\n", buf);
+
+    buf[0] = 0; buf[1] = 'A'; buf[2] = 'j'; buf[3] = 0;
+    parm.info.lpstrReturn = buf;
+    parm.info.dwRetSize = 2;
+    err = mciSendCommandA(1, MCI_INFO, MCI_INFO_PRODUCT, (DWORD_PTR)&parm);
+    ok(!err, "mciCommand MCI_INFO product: %s\n", dbg_mcierr(err));
+    ok(buf[0] /* && !buf[1] */ && (buf[2] == 'j' || broken(!buf[2])), "info product output buffer %s\n", buf);
+    /* Producing non-ASCII multi-byte output, native forgets to zero-terminate a too small buffer
+     * with SendStringA, while SendStringW works correctly (jap. and chin. locale): ignore buf[1] */
+    /* Bug in 64 bit Vista/w2k8/w7: mciSendStringW is used! (not in xp nor w2k3) */
+
+    buf[0] = 'K'; buf[1] = 0;
+    parm.info.dwRetSize = sizeof(buf);
+    err = mciSendCommandW(1, MCI_INFO, 0x07000000, (DWORD_PTR)&parm);
+    ok(err==MCIERR_UNRECOGNIZED_KEYWORD || broken(err==MMSYSERR_NOTSUPPORTED/* Win9x */), "mciCommand MCI_INFO other: %s\n", dbg_mcierr(err));
+    ok(!strcmp(buf,"K"), "info output buffer %s\n", buf);
+
+    err = mciGetDeviceIDA("all");
+    ok(err == MCI_ALL_DEVICE_ID, "mciGetDeviceIDA all returned %u, expected MCI_ALL_DEVICE_ID\n", err);
+
+    err = mciSendStringA(command_close_my, NULL, 0, hwnd);
     ok(!err,"mci %s returned %s\n", command_close_my, dbg_mcierr(err));
     test_notification(hwnd, command_close_my, MCI_NOTIFY_SUCCESSFUL);
     Sleep(5);
     test_notification(hwnd, command_close_my, 0);
 
-    err = mciSendString("open no-such-file-exists.wav alias y", buf, sizeof(buf), NULL);
+    err = mciSendStringA("open no-such-file-exists.wav alias y buffer 6", buf, sizeof(buf), NULL);
     ok(err==MCIERR_FILE_NOT_FOUND,"open no-such-file.wav returned %s\n", dbg_mcierr(err));
     if(!err) {
-        err = mciSendString("close y", NULL, 0, NULL);
+        err = mciSendStringA("close y", NULL, 0, NULL);
         ok(!err,"close y returned %s\n", dbg_mcierr(err));
     }
 
-    err = mciSendString("open no-such-dir\\file.wav alias y type waveaudio", buf, sizeof(buf), NULL);
+    err = mciSendStringA("open no-such-dir\\file.wav alias y type waveaudio", buf, sizeof(buf), NULL);
     ok(err==MCIERR_FILE_NOT_FOUND || broken(err==MCIERR_INVALID_FILE /* Win9X */),"open no-such-dir/file.wav returned %s\n", dbg_mcierr(err));
     if(!err) {
-        err = mciSendString("close y", NULL, 0, NULL);
+        err = mciSendStringA("close y", NULL, 0, NULL);
         ok(!err,"close y returned %s\n", dbg_mcierr(err));
     }
 
-    err = mciSendString(command_close_all, NULL, 0, NULL);
-    todo_wine ok(!err,"mci %s (without buffer) returned %s\n", command_close_all, dbg_mcierr(err));
+    err = mciSendStringA("open ! alias no", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_INVALID_DEVICE_NAME,"open !(void): %s\n", dbg_mcierr(err));
 
-    memset(buf, 0, sizeof(buf));
-    err = mciSendString(command_close_all, buf, sizeof(buf), hwnd);
-    todo_wine ok(!err,"mci %s (with output buffer) returned %s\n", command_close_all, dbg_mcierr(err));
-    ok(buf[0] == 0, "mci %s changed output buffer: %s\n", command_close_all, buf);
+    err = mciSendStringA("open !no-such-file-exists.wav alias no", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_FILE_NOT_FOUND || /* Win9X */err==MCIERR_INVALID_DEVICE_NAME,"open !name: %s\n", dbg_mcierr(err));
+
+    /* FILE_NOT_FOUND stems from mciwave,
+     * the complete name including ! is passed through since NT */
+    err = mciSendStringA("open nosuchdevice!tempfile.wav alias no", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_FILE_NOT_FOUND || /* Win9X */err==MCIERR_INVALID_DEVICE_NAME,"open nosuchdevice!name: %s\n", dbg_mcierr(err));
+    /* FIXME? use broken(INVALID_DEVICE_NAME) and have Wine not mimic Win9X? */
+
+    err = mciSendStringA("close waveaudio", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_INVALID_DEVICE_NAME,"close waveaudio: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA(command_close_all, NULL, 0, NULL);
+    ok(!err,"mci %s (without buffer) returned %s\n", command_close_all, dbg_mcierr(err));
+
+    err = mciSendStringA(command_close_all, buf, sizeof(buf), hwnd);
+    ok(!err,"mci %s (with output buffer) returned %s\n", command_close_all, dbg_mcierr(err));
+    ok(buf[0] == 0, "mci %s output buffer: %s\n", command_close_all, buf);
     /* No notification left, everything closed already */
     test_notification(hwnd, command_close_all, 0);
     /* TODO test close all sends one notification per open device */
 
-    memset(buf, 0, sizeof(buf));
-    err = mciSendString(command_sysinfo, buf, sizeof(buf), NULL);
+    err = mciSendStringA(command_sysinfo, buf, sizeof(buf), NULL);
     ok(!err,"mci %s returned %s\n", command_sysinfo, dbg_mcierr(err));
-    todo_wine ok(buf[0] == '0' && buf[1] == 0, "mci %s, expected output buffer '0', got: '%s'\n", command_sysinfo, buf);
+    ok(buf[0] == '0' && buf[1] == 0, "mci %s, expected output buffer '0', got: '%s'\n", command_sysinfo, buf);
 
-    err = mciSendString("open new type waveaudio", buf, sizeof(buf), NULL);
+    err = mciSendStringA("open new type waveaudio", buf, sizeof(buf), NULL);
     ok(err==MCIERR_NEW_REQUIRES_ALIAS,"mci open new without alias returned %s\n", dbg_mcierr(err));
 
-    err = mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_WAIT, 0); /* from MSDN */
-    ok(!err,"mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, 0) returned %s\n", dbg_mcierr(err));
+    parm.open.lpstrDeviceType = (LPSTR)MCI_DEVTYPE_WAVEFORM_AUDIO;
+    err = mciSendCommandA(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD_PTR)&parm);
+    ok(!err,"mciCommand OPEN_TYPE_ID waveaudio: %s\n", dbg_mcierr(err));
+
+    if(!err) {
+        MCIDEVICEID wDeviceID = parm.open.wDeviceID;
+        parm.caps.dwItem = MCI_GETDEVCAPS_DEVICE_TYPE;
+        err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
+        ok(!err,"mciCommand MCI_GETDEVCAPS device type: %s\n", dbg_mcierr(err));
+        ok(MCI_DEVTYPE_WAVEFORM_AUDIO==parm.caps.dwReturn,"mciCommand GETDEVCAPS says %u, expected %u\n", parm.caps.dwReturn, MCI_DEVTYPE_WAVEFORM_AUDIO);
+    }
+
+    ok(0xDEADF00D==intbuf[0] && 0xABADCAFE==intbuf[2],"DWORD buffer corruption\n");
+
+    err = mciGetDeviceIDA("waveaudio");
+    ok(err == 1, "mciGetDeviceIDA waveaudio returned %u, expected 1\n", err);
+
+    err = mciSendStringA("open no-such-file.wav alias waveaudio", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_DUPLICATE_ALIAS, "mci open alias waveaudio returned %s\n", dbg_mcierr(err));
+    /* If it were not already in use, open avivideo alias waveaudio would succeed,
+     * making for funny test cases. */
+
+    err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_WAIT, 0); /* from MSDN */
+    ok(!err, "mciCommand close returned %s\n", dbg_mcierr(err));
 
-    err = mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, 0);
-    ok(!err,"mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, 0) returned %s\n", dbg_mcierr(err));
+    err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, 0);
+    ok(!err, "mciCommand close returned %s\n", dbg_mcierr(err));
 
-    parm.dwCallback = (DWORD_PTR)hwnd;
-    err = mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, (DWORD_PTR)&parm);
-    ok(!err,"mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, hwnd) returned %s\n", dbg_mcierr(err));
+    parm.gen.dwCallback = (DWORD_PTR)hwnd;
+    err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, (DWORD_PTR)&parm);
+    ok(!err, "mciCommand close returned %s\n", dbg_mcierr(err));
     test_notification(hwnd, command_close_all, 0); /* None left */
 }
 
@@ -257,6 +621,7 @@ static void test_recordWAVE(HWND hwnd)
     WORD nch    = 1;
     WORD nbits  = 16;
     DWORD nsamp = 16000, expect;
+    UINT ndevs  = waveInGetNumDevs();
     MCIERROR err, ok_pcm;
     MCIDEVICEID wDeviceID;
     MCI_PARMS_UNION parm;
@@ -266,134 +631,166 @@ static void test_recordWAVE(HWND hwnd)
 
     parm.open.lpstrDeviceType = "waveaudio";
     parm.open.lpstrElementName = ""; /* "new" at the command level */
-    parm.open.lpstrAlias = "x"; /* to enable mciSendString */
+    parm.open.lpstrAlias = "x"; /* to enable mciSendStringA */
     parm.open.dwCallback = (DWORD_PTR)hwnd;
-    err = mciSendCommand(0, MCI_OPEN,
-        MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_ALIAS | MCI_NOTIFY,
-        (DWORD_PTR)&parm);
+    err = mciSendCommandA(0, MCI_OPEN,
+            MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_ALIAS | MCI_NOTIFY, (DWORD_PTR)&parm);
     ok(!err,"mciCommand open new type waveaudio alias x notify: %s\n", dbg_mcierr(err));
     wDeviceID = parm.open.wDeviceID;
 
-    /* In Wine, both MCI_Open and the individual drivers send notifications. */
+    err = mciGetDeviceIDA("x");
+    ok(err == wDeviceID, "mciGetDeviceIDA x returned %u, expected %u\n", err, wDeviceID);
+
+    /* Only the alias is looked up. */
+    err = mciGetDeviceIDA("waveaudio");
+    ok(!err, "mciGetDeviceIDA waveaudio returned %u, expected 0\n", err);
+
     test_notification(hwnd, "open new", MCI_NOTIFY_SUCCESSFUL);
-    todo_wine test_notification1(hwnd, "open new no #2", 0);
+    test_notification(hwnd, "open new no #2", 0);
 
     /* Do not query time format as string because result depends on locale! */
     parm.status.dwItem = MCI_STATUS_TIME_FORMAT;
-    err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
     ok(!err,"mciCommand status time format: %s\n", dbg_mcierr(err));
     ok(parm.status.dwReturn==MCI_FORMAT_MILLISECONDS,"status time format: %ld\n",parm.status.dwReturn);
 
     /* Info file fails until named in Open or Save. */
-    err = mciSendString("info x file", buf, sizeof(buf), NULL);
+    err = mciSendStringA("info x file", buf, sizeof(buf), NULL);
     todo_wine ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci info new file returned %s\n", dbg_mcierr(err));
+    ok(!buf[0], "info error buffer %s\n", buf);
+
+    err = mciSendStringA("status x length", buf, sizeof(buf), NULL);
+    todo_wine ok(!err,"status x length initial: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf,"0"), "mci status length expected 0, got: %s\n", buf);
 
     /* Check the default recording: 8-bits per sample, mono, 11kHz */
-    err = mciSendString("status x samplespersec", buf, sizeof(buf), NULL);
+    err = mciSendStringA("status x samplespersec", buf, sizeof(buf), NULL);
     ok(!err,"mci status samplespersec returned %s\n", dbg_mcierr(err));
     if(!err) ok(!strcmp(buf,"11025"), "mci status samplespersec expected 11025, got: %s\n", buf);
 
     /* MCI seems to solely support PCM, no need for ACM conversion. */
-    err = mciSendString("set x format tag 2", NULL, 0, NULL);
+    err = mciSendStringA("set x format tag 2", NULL, 0, NULL);
     ok(err==MCIERR_OUTOFRANGE,"mci set format tag 2 returned %s\n", dbg_mcierr(err));
 
     /* MCI appears to scan the available devices for support of this format,
      * returning MCIERR_OUTOFRANGE on machines with no sound.
+     * However some w2k8/w7 machines return no error when there's no wave
+     * input device (perhaps querying waveOutGetNumDevs instead of waveIn?),
+     * still the record command below fails with MCIERR_WAVE_INPUTSUNSUITABLE.
      * Don't skip here, record will fail below. */
-    err = mciSendString("set x format tag pcm", NULL, 0, NULL);
+    err = mciSendStringA("set x format tag pcm", NULL, 0, NULL);
     ok(!err || err==MCIERR_OUTOFRANGE,"mci set format tag pcm returned %s\n", dbg_mcierr(err));
     ok_pcm = err;
 
-    err = mciSendString("set x samplespersec 41000 alignment 4 channels 2", NULL, 0, NULL);
-    ok(err==ok_pcm,"mci set samples+align+channels returned %s\n", dbg_mcierr(err));
-
+    /* MSDN warns against not setting all wave format parameters.
+     * Indeed, it produces strange results, incl.
+     * inconsistent PCMWAVEFORMAT headers in the saved file.
+     */
+    err = mciSendStringA("set x bytespersec 22050 alignment 2 samplespersec 11025 channels 1 bitspersample 16", NULL, 0, NULL);
+    ok(err==ok_pcm,"mci set 5 wave parameters returned %s\n", dbg_mcierr(err));
     /* Investigate: on w2k, set samplespersec 22050 sets nChannels to 2!
-     *  err = mciSendString("set x samplespersec 22050", NULL, 0, NULL);
+     *  err = mciSendStringA("set x samplespersec 22050", NULL, 0, NULL);
      *  ok(!err,"mci set samplespersec returned %s\n", dbg_mcierr(err));
      */
 
+    /* Checks are generally performed immediately. */
+    err = mciSendStringA("set x bitspersample 4", NULL, 0, NULL);
+    todo_wine ok(err==MCIERR_OUTOFRANGE,"mci set bitspersample 4 returned %s\n", dbg_mcierr(err));
+
     parm.set.wFormatTag = WAVE_FORMAT_PCM;
     parm.set.nSamplesPerSec = nsamp;
     parm.set.wBitsPerSample = nbits;
     parm.set.nChannels      = nch;
     parm.set.nBlockAlign    = parm.set.nChannels * parm.set.wBitsPerSample /8;
     parm.set.nAvgBytesPerSec= parm.set.nSamplesPerSec * parm.set.nBlockAlign;
-    err = mciSendCommand(wDeviceID, MCI_SET,
-        MCI_WAVE_SET_SAMPLESPERSEC | MCI_WAVE_SET_CHANNELS |
-        MCI_WAVE_SET_BITSPERSAMPLE | MCI_WAVE_SET_BLOCKALIGN |
-        MCI_WAVE_SET_AVGBYTESPERSEC| MCI_WAVE_SET_FORMATTAG, (DWORD_PTR)&parm);
+    err = mciSendCommandA(wDeviceID, MCI_SET,
+            MCI_WAVE_SET_SAMPLESPERSEC | MCI_WAVE_SET_CHANNELS | MCI_WAVE_SET_BITSPERSAMPLE |
+            MCI_WAVE_SET_BLOCKALIGN | MCI_WAVE_SET_AVGBYTESPERSEC| MCI_WAVE_SET_FORMATTAG,
+            (DWORD_PTR)&parm);
     ok(err==ok_pcm,"mciCommand set wave format: %s\n", dbg_mcierr(err));
 
+    parm.caps.dwItem = MCI_WAVE_GETDEVCAPS_INPUTS;
+    parm.caps.dwCallback = (DWORD_PTR)hwnd;
+    err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM | MCI_NOTIFY,
+            (DWORD_PTR)&parm);
+    ok(!err,"mciCommand MCI_GETDEVCAPS inputs: %s\n", dbg_mcierr(err));
+    ok(parm.caps.dwReturn==ndevs,"mciCommand GETDEVCAPS claims %u inputs, expected %u\n", parm.caps.dwReturn, ndevs);
+    ok(!ok_pcm || !parm.caps.dwReturn,"No input device accepts PCM!?\n");
+    test_notification(hwnd, "GETDEVCAPS inputs", MCI_NOTIFY_SUCCESSFUL);
+
     /* A few ME machines pass all tests except set format tag pcm! */
-    err = mciSendString("record x to 2000 wait", NULL, 0, hwnd);
+    err = mciSendStringA("record x to 2000 wait", NULL, 0, hwnd);
     ok(err || !ok_pcm,"can record yet set wave format pcm returned %s\n", dbg_mcierr(ok_pcm));
-    ok(!err || err==(ok_pcm==MCIERR_OUTOFRANGE ? MCIERR_WAVE_INPUTSUNSUITABLE : 0),"mci record to 2000 returned %s\n", dbg_mcierr(err));
+    if(!ndevs) todo_wine /* with sound disabled */
+    ok(ndevs>0 ? !err : err==MCIERR_WAVE_INPUTSUNSUITABLE,"mci record to 2000 returned %s\n", dbg_mcierr(err));
+    else
+    ok(ndevs>0 ? !err : err==MCIERR_WAVE_INPUTSUNSUITABLE,"mci record to 2000 returned %s\n", dbg_mcierr(err));
     if(err) {
         if (err==MCIERR_WAVE_INPUTSUNSUITABLE)
              skip("Please install audio driver. Everything is skipped.\n");
         else skip("Cannot record cause %s. Everything is skipped.\n", dbg_mcierr(err));
 
-        err = mciSendString("close x", NULL, 0, NULL);
+        err = mciSendStringA("close x", NULL, 0, NULL);
         ok(!err,"mci close returned %s\n", dbg_mcierr(err));
         test_notification(hwnd,"record skipped",0);
         return;
     }
 
     /* Query some wave format parameters depending on the time format. */
-    err = mciSendString("status x position", buf, sizeof(buf), NULL);
+    err = mciSendStringA("status x position", buf, sizeof(buf), NULL);
     ok(!err,"mci status position returned %s\n", dbg_mcierr(err));
     if(!err) todo_wine ok(!strcmp(buf,"2000"), "mci status position gave %s, expected 2000, some tests will fail\n", buf);
 
-    err = mciSendString("set x time format 8", NULL, 0, NULL); /* bytes */
+    err = mciSendStringA("set x time format 8", NULL, 0, NULL); /* bytes */
     ok(!err,"mci returned %s\n", dbg_mcierr(err));
 
     parm.status.dwItem = MCI_STATUS_POSITION;
-    err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
     ok(!err,"mciCommand status position: %s\n", dbg_mcierr(err));
     expect = 2 * nsamp * nch * nbits/8;
     if(!err) todo_wine ok(parm.status.dwReturn==expect,"recorded %lu bytes, expected %u\n",parm.status.dwReturn,expect);
 
     parm.set.dwTimeFormat = MCI_FORMAT_SAMPLES;
-    err = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&parm);
+    err = mciSendCommandA(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&parm);
     ok(!err,"mciCommand set time format samples: %s\n", dbg_mcierr(err));
 
     parm.status.dwItem = MCI_STATUS_POSITION;
-    err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
     ok(!err,"mciCommand status position: %s\n", dbg_mcierr(err));
     expect = 2 * nsamp;
     if(!err) todo_wine ok(parm.status.dwReturn==expect,"recorded %lu samples, expected %u\n",parm.status.dwReturn,expect);
 
-    err = mciSendString("set x time format milliseconds", NULL, 0, NULL);
+    err = mciSendStringA("set x time format milliseconds", NULL, 0, NULL);
     ok(!err,"mci set time format milliseconds returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("save x tempfile1.wav", NULL, 0, NULL);
+    err = mciSendStringA("save x tempfile1.wav", NULL, 0, NULL);
     ok(!err,"mci save returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("save x tempfile.wav", NULL, 0, NULL);
+    err = mciSendStringA("save x tempfile.wav", NULL, 0, NULL);
     ok(!err,"mci save returned %s\n", dbg_mcierr(err));
     if(!err) ok_saved = 0;
 
     /* Save must not rename the original file. */
-    if (!DeleteFile("tempfile1.wav"))
-        todo_wine ok(FALSE, "Save must not rename the original file; DeleteFile returned %d\n", GetLastError());
+    if (!DeleteFileA("tempfile1.wav"))
+        todo_wine ok(FALSE, "Save must not rename the original file; DeleteFileA returned %d\n",
+                GetLastError());
 
-    err = mciSendString("set x channels 2", NULL, 0, NULL);
+    err = mciSendStringA("set x channels 2", NULL, 0, NULL);
     ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci set channels after saving returned %s\n", dbg_mcierr(err));
 
     parm.seek.dwTo = 600;
-    err = mciSendCommand(wDeviceID, MCI_SEEK, MCI_TO | MCI_WAIT, (DWORD_PTR)&parm);
+    err = mciSendCommandA(wDeviceID, MCI_SEEK, MCI_TO | MCI_WAIT, (DWORD_PTR)&parm);
     ok(!err,"mciCommand seek to 600: %s\n", dbg_mcierr(err));
 
     /* Truncate to current position */
-    err = mciSendString("delete x", NULL, 0, NULL);
+    err = mciSendStringA("delete x", NULL, 0, NULL);
     todo_wine ok(!err,"mci delete returned %s\n", dbg_mcierr(err));
 
-    buf[0]='\0';
-    err = mciSendString("status x length", buf, sizeof(buf), NULL);
+    err = mciSendStringA("status x length", buf, sizeof(buf), NULL);
     ok(!err,"mci status length returned %s\n", dbg_mcierr(err));
     todo_wine ok(!strcmp(buf,"600"), "mci status length after delete gave %s, expected 600\n", buf);
 
-    err = mciSendString("close x", NULL, 0, NULL);
+    err = mciSendStringA("close x", NULL, 0, NULL);
     ok(!err,"mci close returned %s\n", dbg_mcierr(err));
     test_notification(hwnd,"record complete",0);
 }
@@ -404,113 +801,132 @@ static void test_playWAVE(HWND hwnd)
     char buf[1024];
     memset(buf, 0, sizeof(buf));
 
-    err = mciSendString("open waveaudio!tempfile.wav alias mysound", NULL, 0, NULL);
+    err = mciSendStringA("open waveaudio!tempfile.wav alias mysound", NULL, 0, NULL);
     ok(err==ok_saved,"mci open waveaudio!tempfile.wav returned %s\n", dbg_mcierr(err));
     if(err) {
         skip("Cannot open waveaudio!tempfile.wav for playing (%s), skipping\n", dbg_mcierr(err));
         return;
     }
 
-    err = mciSendString("status mysound length", buf, sizeof(buf), NULL);
+    err = mciGetDeviceIDA("mysound");
+    ok(err == 1, "mciGetDeviceIDA mysound returned %u, expected 1\n", err);
+
+    err = mciGetDeviceIDA("tempfile.wav");
+    ok(!err, "mciGetDeviceIDA tempfile.wav returned %u, expected 0\n", err);
+
+    err = mciGetDeviceIDA("waveaudio");
+    ok(!err, "mciGetDeviceIDA waveaudio returned %u, expected 0\n", err);
+
+    err = mciSendStringA("status mysound length", buf, sizeof(buf), NULL);
     ok(!err,"mci status length returned %s\n", dbg_mcierr(err));
     todo_wine ok(!strcmp(buf,"2000"), "mci status length gave %s, expected 2000, some tests will fail.\n", buf);
 
-    err = mciSendString("cue output", NULL, 0, NULL);
-    todo_wine ok(err==MCIERR_UNRECOGNIZED_COMMAND,"mci incorrect cue output returned %s\n", dbg_mcierr(err));
+    err = mciSendStringA("cue output", NULL, 0, NULL);
+    ok(err==MCIERR_UNRECOGNIZED_COMMAND,"mci incorrect cue output returned %s\n", dbg_mcierr(err));
 
     /* Test MCI to the bones -- Some todo_wine from Cue and
      * from Play from 0 to 0 are not worth fixing. */
-    err = mciSendString("cue mysound output notify", NULL, 0, hwnd);
+    err = mciSendStringA("cue mysound output notify", NULL, 0, hwnd);
     ok(!err,"mci cue output after open file returned %s\n", dbg_mcierr(err));
     /* Notification is delayed as a play thread is started. */
-    todo_wine test_notification1(hwnd, "cue immediate", 0);
+    todo_wine test_notification(hwnd, "cue immediate", 0);
 
     /* Cue pretends to put the MCI into paused state. */
-    err = mciSendString("status mysound mode", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status mysound mode", buf, sizeof(buf), hwnd);
     ok(!err,"mci status mode returned %s\n", dbg_mcierr(err));
     todo_wine ok(!strcmp(buf,"paused"), "mci status mode: %s, expected (pseudo)paused\n", buf);
 
     /* Strange pause where Pause is rejected, unlike Play; Pause; Pause tested below */
-    err = mciSendString("pause mysound", NULL, 0, hwnd);
+    err = mciSendStringA("pause mysound", NULL, 0, hwnd);
     ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci pause after cue returned %s\n", dbg_mcierr(err));
 
     /* MCI appears to start the play thread in this border case.
      * Guessed that from (flaky) status mode and late notification arrival. */
-    err = mciSendString("play mysound from 0 to 0 notify", NULL, 0, hwnd);
+    err = mciSendStringA("play mysound from 0 to 0 notify", NULL, 0, hwnd);
     ok(!err,"mci play from 0 to 0 returned %s\n", dbg_mcierr(err));
-    todo_wine test_notification1(hwnd, "cue aborted by play", MCI_NOTIFY_ABORTED);
+    todo_wine test_notification(hwnd, "cue aborted by play", MCI_NOTIFY_ABORTED);
     /* play's own notification follows below */
 
-    err = mciSendString("play mysound from 250 to 0", NULL, 0, NULL);
+    err = mciSendStringA("play mysound from 250 to 0", NULL, 0, NULL);
     ok(err==MCIERR_OUTOFRANGE,"mci play from 250 to 0 returned %s\n", dbg_mcierr(err));
 
     Sleep(50); /* Give play from 0 to 0 time to finish. */
-    todo_wine test_notification1(hwnd, "play from 0 to 0", MCI_NOTIFY_SUCCESSFUL);
+    todo_wine test_notification(hwnd, "play from 0 to 0", MCI_NOTIFY_SUCCESSFUL);
 
-    err = mciSendString("status mysound mode", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status mysound mode", buf, sizeof(buf), hwnd);
     ok(!err,"mci status mode returned %s\n", dbg_mcierr(err));
     ok(!strcmp(buf,"stopped"), "mci status mode: %s after play from 0 to 0\n", buf);
 
-    err = mciSendString("play MYSOUND from 250 to 0 notify", NULL, 0, hwnd);
+    err = mciSendStringA("play MYSOUND from 250 to 0 notify", NULL, 0, hwnd);
     ok(err==MCIERR_OUTOFRANGE,"mci play from 250 to 0 notify returned %s\n", dbg_mcierr(err));
     /* No notification (checked below) sent if error */
 
     /* A second play caused Wine<1.1.33 to hang */
-    err = mciSendString("play mysound from 500 to 1500 wait", NULL, 0, NULL);
-    ok(!err,"mci play from 500 to 1500 returned %s\n", dbg_mcierr(err));
+    err = mciSendStringA("play mysound from 500 to 220:5:0 wait", NULL, 0, NULL);
+    ok(!err,"mci play from 500 to 220:5:0 (=1500) returned %s\n", dbg_mcierr(err));
 
-    memset(buf, 0, sizeof(buf));
-    err = mciSendString("status mysound position", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status mysound position", buf, sizeof(buf), hwnd);
     ok(!err,"mci status position returned %s\n", dbg_mcierr(err));
     if(!err) ok(!strcmp(buf,"1500"), "mci status position: %s\n", buf);
 
     /* mci will not play position < current */
-    err = mciSendString("play mysound to 1000", NULL, 0, NULL);
+    err = mciSendStringA("play mysound to 1000", NULL, 0, NULL);
     ok(err==MCIERR_OUTOFRANGE,"mci play to 1000 returned %s\n", dbg_mcierr(err));
 
     /* mci will not play to > end */
-    err = mciSendString("play mysound TO 3000 notify", NULL, 0, hwnd);
+    err = mciSendStringA("play mysound TO 3000 notify", NULL, 0, hwnd);
     ok(err==MCIERR_OUTOFRANGE,"mci play to 3000 notify returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("play mysound to 2000", NULL, 0, NULL);
+    err = mciSendStringA("play mysound to 2000", NULL, 0, NULL);
     ok(!err,"mci play to 2000 returned %s\n", dbg_mcierr(err));
 
     /* Rejected while playing */
-    err = mciSendString("cue mysound output", NULL, 0, NULL);
+    err = mciSendStringA("cue mysound output", NULL, 0, NULL);
     ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci cue output while playing returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("play mysound to 3000", NULL, 0, NULL);
+    err = mciSendStringA("play mysound to 3000", NULL, 0, NULL);
     ok(err==MCIERR_OUTOFRANGE,"mci play to 3000 returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("stop mysound Wait", NULL, 0, NULL);
+    err = mciSendStringA("stop mysound Wait", NULL, 0, NULL);
     ok(!err,"mci stop wait returned %s\n", dbg_mcierr(err));
     test_notification(hwnd, "play/cue/pause/stop", 0);
 
-    err = mciSendString("Seek Mysound to 250 wait Notify", NULL, 0, hwnd);
+    err = mciSendStringA("Seek Mysound to 250 wait Notify", NULL, 0, hwnd);
     ok(!err,"mci seek to 250 wait notify returned %s\n", dbg_mcierr(err));
     test_notification(hwnd,"seek wait notify",MCI_NOTIFY_SUCCESSFUL);
 
-    memset(buf, 0, sizeof(buf));
-    err = mciSendString("status mysound position notify", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("seek mysound to 0xfa", NULL, 0, NULL);
+    ok(err==MCIERR_BAD_INTEGER,"mci seek to 0xfa returned %s\n", dbg_mcierr(err));
+
+    /* MCI_INTEGER always accepts colon notation */
+    err = mciSendStringA("seek mysound to :1", NULL, 0, NULL);
+    ok(!err,"mci seek to :1 (=256) returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("seek mysound to 250::", NULL, 0, NULL);
+    ok(!err,"mci seek to 250:: returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("seek mysound to 250:0", NULL, 0, NULL);
+    ok(!err,"mci seek to 250:0 returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status mysound position notify", buf, sizeof(buf), hwnd);
     ok(!err,"mci status position notify returned %s\n", dbg_mcierr(err));
     if(!err) ok(!strcmp(buf,"250"), "mci status position: %s\n", buf);
     /* Immediate commands like status also send notifications. */
     test_notification(hwnd,"status position",MCI_NOTIFY_SUCCESSFUL);
 
-    memset(buf, 0, sizeof(buf));
-    err = mciSendString("status mysound mode", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status mysound mode", buf, sizeof(buf), hwnd);
     ok(!err,"mci status mode returned %s\n", dbg_mcierr(err));
     ok(!strcmp(buf,"stopped"), "mci status mode: %s\n", buf);
 
     /* Another play from == to testcase */
-    err = mciSendString("play mysound to 250 wait notify", NULL, 0, hwnd);
+    err = mciSendStringA("play mysound to 250 wait notify", NULL, 0, hwnd);
     ok(!err,"mci play (from 250) to 250 returned %s\n", dbg_mcierr(err));
-    todo_wine test_notification1(hwnd,"play to 250 wait notify",MCI_NOTIFY_SUCCESSFUL);
+    todo_wine test_notification(hwnd,"play to 250 wait notify",MCI_NOTIFY_SUCCESSFUL);
 
-    err = mciSendString("cue mysound output", NULL, 0, NULL);
+    err = mciSendStringA("cue mysound output", NULL, 0, NULL);
     ok(!err,"mci cue output after play returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("close mysound", NULL, 0, NULL);
+    err = mciSendStringA("close mysound", NULL, 0, NULL);
     ok(!err,"mci close returned %s\n", dbg_mcierr(err));
     test_notification(hwnd,"after close",0);
 }
@@ -523,7 +939,7 @@ static void test_asyncWAVE(HWND hwnd)
     char buf[1024];
     memset(buf, 0, sizeof(buf));
 
-    err = mciSendString("open tempfile.wav alias mysound notify", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("open tempfile.wav alias mysound notify type waveaudio", buf, sizeof(buf), hwnd);
     ok(err==ok_saved,"mci open tempfile.wav returned %s\n", dbg_mcierr(err));
     if(err) {
         skip("Cannot open tempfile.wav for playing (%s), skipping\n", dbg_mcierr(err));
@@ -534,60 +950,71 @@ static void test_asyncWAVE(HWND hwnd)
     ok(wDeviceID,"mci open DeviceID: %d\n", wDeviceID);
     test_notification(hwnd,"open alias notify",MCI_NOTIFY_SUCCESSFUL);
 
-    err = mciSendString("status mysound mode", buf, sizeof(buf), hwnd);
+    err = mciGetDeviceIDA("mysound");
+    ok(err == wDeviceID, "mciGetDeviceIDA alias returned %u, expected %u\n", err, wDeviceID);
+
+    /* Only the alias is looked up. */
+    err = mciGetDeviceIDA("tempfile.wav");
+    ok(!err, "mciGetDeviceIDA tempfile.wav returned %u, expected 0\n", err);
+
+    err = mciGetDeviceIDA("waveaudio");
+    ok(!err, "mciGetDeviceIDA waveaudio returned %u, expected 0\n", err);
+
+    err = mciSendStringA("status mysound mode", buf, sizeof(buf), hwnd);
     ok(!err,"mci status mode returned %s\n", dbg_mcierr(err));
     ok(!strcmp(buf,"stopped"), "mci status mode: %s\n", buf);
 
-    err = mciSendString("play mysound notify", NULL, 0, hwnd);
+    err = mciSendStringA("play mysound notify", NULL, 0, hwnd);
     ok(!err,"mci play returned %s\n", dbg_mcierr(err));
 
-    /* Give Wine's asynchronous thread time to start up.  Furthermore,
-     * it uses 3 buffers per second, so that the positions reported
-     * will be 333ms, 667ms etc. at best. */
-    Sleep(100); /* milliseconds */
+    Sleep(500); /* milliseconds */
 
     /* Do not query time format as string because result depends on locale! */
     parm.status.dwItem = MCI_STATUS_TIME_FORMAT;
-    err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
     ok(!err,"mciCommand status time format: %s\n", dbg_mcierr(err));
     if(!err) ok(parm.status.dwReturn==MCI_FORMAT_MILLISECONDS,"status time format: %ld\n",parm.status.dwReturn);
 
     parm.set.dwTimeFormat = MCI_FORMAT_MILLISECONDS;
-    err = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&parm);
+    err = mciSendCommandA(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&parm);
     ok(!err,"mciCommand set time format ms: %s\n", dbg_mcierr(err));
 
-    buf[0]=0;
-    err = mciSendString("status mysound position", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status mysound position", buf, sizeof(buf), hwnd);
     ok(!err,"mci status position returned %s\n", dbg_mcierr(err));
-    ok(strcmp(buf,"2000"), "mci status position: %s, expected 2000\n", buf);
-    trace("position after Sleep: %sms\n",buf);
+    trace("position after Sleep: %sms\n", buf);
     p2 = atoi(buf);
-    /* Some machines reach 79ms only during the 100ms sleep. */
-    ok(p2>=67,"not enough time elapsed %ums\n",p2);
+    /* Check that the 2s sound plays at a normal pace, giving a wide margin to
+     * account for timing granularity and small delays.
+     */
+    todo_wine ok(350 <= p2 && p2 <= 600, "%ums is not in the expected 350-600ms range\n", p2);
+    /* Wine's asynchronous thread needs some time to start up. Furthermore, it
+     * uses 3 buffers per second, so that the positions reported will be 333ms,
+     * 667ms etc. at best, which is why it fails the above test. So add a
+     * second test specifically to prevent Wine from getting even worse.
+     * FIXME: To be removed when Wine is fixed and passes the above test.
+     */
+    ok(350 <= p2 && p2 <= 1000, "%ums is not even in the expected 350-1000ms range\n", p2);
     test_notification(hwnd,"play (nowait)",0);
 
-    err = mciSendString("pause mysound wait", NULL, 0, hwnd);
+    err = mciSendStringA("pause mysound wait", NULL, 0, hwnd);
     ok(!err,"mci pause wait returned %s\n", dbg_mcierr(err));
 
-    buf[0]=0;
-    err = mciSendString("status mysound mode notify", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status mysound mode notify", buf, sizeof(buf), hwnd);
     ok(!err,"mci status mode returned %s\n", dbg_mcierr(err));
     if(!err) ok(!strcmp(buf,"paused"), "mci status mode: %s\n", buf);
     test_notification(hwnd,"play",MCI_NOTIFY_SUPERSEDED);
     test_notification(hwnd,"status",MCI_NOTIFY_SUCCESSFUL);
 
-    buf[0]=0;
-    err = mciSendString("status mysound position", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status mysound position", buf, sizeof(buf), hwnd);
     ok(!err,"mci status position returned %s\n", dbg_mcierr(err));
     trace("position while paused: %sms\n",buf);
     p1 = atoi(buf);
     ok(p1>=p2, "position not increasing: %u > %u\n", p2, p1);
 
-    err = mciSendString("stop mysound wait", NULL, 0, NULL);
+    err = mciSendStringA("stop mysound wait", NULL, 0, NULL);
     ok(!err,"mci stop returned %s\n", dbg_mcierr(err));
 
-    buf[0]=0;
-    err = mciSendString("info mysound file notify", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("info mysound file notify", buf, sizeof(buf), hwnd);
     ok(!err,"mci info file returned %s\n", dbg_mcierr(err));
     if(!err) { /* fully qualified name */
         int len = strlen(buf);
@@ -596,13 +1023,11 @@ static void test_asyncWAVE(HWND hwnd)
     }
     test_notification(hwnd,"info file",MCI_NOTIFY_SUCCESSFUL);
 
-    buf[0]=0;
-    err = mciSendString("status mysound mode", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status mysound mode", buf, sizeof(buf), hwnd);
     ok(!err,"mci status mode returned %s\n", dbg_mcierr(err));
     ok(!strcmp(buf,"stopped"), "mci status mode: %s\n", buf);
 
-    buf[0]=0;
-    err = mciSendString("status mysound position", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status mysound position", buf, sizeof(buf), hwnd);
     ok(!err,"mci status position returned %s\n", dbg_mcierr(err));
     trace("position once stopped: %sms\n",buf);
     p2 = atoi(buf);
@@ -610,84 +1035,82 @@ static void test_asyncWAVE(HWND hwnd)
     ok(p2>=p1 && p2<=p1+16,"position changed from %ums to %ums\n",p1,p2);
 
     /* No Resume once stopped (waveaudio, sequencer and cdaudio differ). */
-    err = mciSendString("resume mysound wait", NULL, 0, NULL);
+    err = mciSendStringA("resume mysound wait", NULL, 0, NULL);
     ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci resume wait returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("play mysound wait", NULL, 0, NULL);
+    err = mciSendStringA("play mysound wait", NULL, 0, NULL);
     ok(!err,"mci play wait returned %s\n", dbg_mcierr(err));
 
-    buf[0]=0;
-    err = mciSendString("status mysound position", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status mysound position", buf, sizeof(buf), hwnd);
     ok(!err,"mci status position returned %s\n", dbg_mcierr(err));
     todo_wine ok(!strcmp(buf,"2000"), "mci status position: %s\n", buf);
 
-    err = mciSendString("seek mysound to start wait", NULL, 0, NULL);
+    err = mciSendStringA("seek mysound to start wait", NULL, 0, NULL);
     ok(!err,"mci seek to start wait returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("play mysound to 1000 notify", NULL, 0, hwnd);
+    err = mciSendStringA("play mysound to 1000 notify", NULL, 0, hwnd);
     ok(!err,"mci play returned %s\n", dbg_mcierr(err));
 
     /* Sleep(200); not needed with Wine any more. */
 
-    err = mciSendString("pause mysound notify", NULL, 0, NULL); /* notify no callback */
+    err = mciSendStringA("pause mysound notify", NULL, 0, NULL); /* notify no callback */
     ok(!err,"mci pause notify returned %s\n", dbg_mcierr(err));
     /* Supersede even though pause cannot notify given no callback */
     test_notification(hwnd,"pause aborted play #1 notification",MCI_NOTIFY_SUPERSEDED);
     test_notification(hwnd,"impossible pause notification",0);
 
-    err = mciSendString("cue mysound output notify", NULL, 0, hwnd);
+    err = mciSendStringA("cue mysound output notify", NULL, 0, hwnd);
     ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci cue output while paused returned %s\n", dbg_mcierr(err));
     test_notification(hwnd,"cue output notify #2",0);
 
-    err = mciSendString("resume mysound notify", NULL, 0, hwnd);
+    err = mciSendStringA("resume mysound notify", NULL, 0, hwnd);
     ok(!err,"mci resume notify returned %s\n", dbg_mcierr(err));
     test_notification(hwnd, "resume notify", MCI_NOTIFY_SUCCESSFUL);
 
     /* Seek or even Stop used to hang Wine<1.1.32 on MacOS. */
-    err = mciSendString("seek mysound to 0 wait", NULL, 0, NULL);
+    err = mciSendStringA("seek mysound to 0 wait", NULL, 0, NULL);
     ok(!err,"mci seek to start returned %s\n", dbg_mcierr(err));
 
     /* Seek stops. */
-    err = mciSendString("status mysound mode", buf, sizeof(buf), NULL);
+    err = mciSendStringA("status mysound mode", buf, sizeof(buf), NULL);
     ok(!err,"mci status mode returned %s\n", dbg_mcierr(err));
     if(!err) ok(!strcmp(buf,"stopped"), "mci status mode: %s\n", buf);
 
-    err = mciSendString("seek mysound wait", NULL, 0, NULL);
+    err = mciSendStringA("seek mysound wait", NULL, 0, NULL);
     ok(err==MCIERR_MISSING_PARAMETER,"mci seek to nowhere returned %s\n", dbg_mcierr(err));
 
     /* cdaudio does not detect to start to end as error */
-    err = mciSendString("seek mysound to start to 0", NULL, 0, NULL);
+    err = mciSendStringA("seek mysound to start to 0", NULL, 0, NULL);
     ok(err==MCIERR_FLAGS_NOT_COMPATIBLE,"mci seek to start to 0 returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("PLAY mysound to 1000 notify", NULL, 0, hwnd);
+    err = mciSendStringA("PLAY mysound to 1000 notify", NULL, 0, hwnd);
     ok(!err,"mci play to 1000 notify returned %s\n", dbg_mcierr(err));
 
     /* Sleep(200); not needed with Wine any more. */
     /* Give it 400ms and resume will appear to complete below. */
 
-    err = mciSendString("pause mysound wait", NULL, 0, NULL);
+    err = mciSendStringA("pause mysound wait", NULL, 0, NULL);
     ok(!err,"mci pause wait returned %s\n", dbg_mcierr(err));
     /* Unlike sequencer and cdaudio, waveaudio's pause does not abort. */
     test_notification(hwnd,"pause aborted play #2 notification",0);
 
-    err = mciSendString("resume mysound wait", NULL, 0, NULL);
+    err = mciSendStringA("resume mysound wait", NULL, 0, NULL);
     ok(!err,"mci resume wait returned %s\n", dbg_mcierr(err));
     /* Resume is a short asynchronous call, something else is playing. */
 
-    err = mciSendString("status mysound mode", buf, sizeof(buf), NULL);
+    err = mciSendStringA("status mysound mode", buf, sizeof(buf), NULL);
     ok(!err,"mci status mode returned %s\n", dbg_mcierr(err));
     if(!err) ok(!strcmp(buf,"playing"), "mci status mode: %s\n", buf);
 
     /* Note extra space before alias */
-    err = mciSendString("pause  mysound wait", NULL, 0, NULL);
+    err = mciSendStringA("pause  mysound wait", NULL, 0, NULL);
     todo_wine ok(!err,"mci pause (space) wait returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("pause mysound wait", NULL, 0, NULL);
+    err = mciSendStringA("pause mysound wait", NULL, 0, NULL);
     ok(!err,"mci pause wait returned %s\n", dbg_mcierr(err));
 
     /* Better ask position only when paused, is it updated while playing? */
-    buf[0]='\0';
-    err = mciSendString("status mysound position", buf, sizeof(buf), NULL);
+    err = mciSendStringA("status mysound position", buf, sizeof(buf), NULL);
     ok(!err,"mci status position returned %s\n", dbg_mcierr(err));
     /* TODO compare position < 900 */
     ok(strcmp(buf,"1000"), "mci resume waited\n");
@@ -695,7 +1118,7 @@ static void test_asyncWAVE(HWND hwnd)
     trace("position after resume: %sms\n",buf);
     test_notification(hwnd,"play (aborted by pause/resume/pause)",0);
 
-    err = mciSendString("close mysound wait", NULL, 0, NULL);
+    err = mciSendStringA("close mysound wait", NULL, 0, NULL);
     ok(!err,"mci close wait returned %s\n", dbg_mcierr(err));
     test_notification(hwnd,"play (aborted by close)",MCI_NOTIFY_ABORTED);
 }
@@ -705,43 +1128,50 @@ static void test_AutoOpenWAVE(HWND hwnd)
     /* This test used(?) to cause intermittent crashes when Wine exits, after
      * fixme:winmm:MMDRV_Exit Closing while ll-driver open
      */
-    MCIERROR err, ok_snd;
+    UINT ndevs = waveOutGetNumDevs();
+    MCIERROR err, ok_snd = ndevs ? 0 : MCIERR_HARDWARE;
+    MCI_PARMS_UNION parm;
     char buf[512], path[300], command[330];
+    DWORD intbuf[3] = { 0xDEADF00D, 99, 0xABADCAFE };
     memset(buf, 0, sizeof(buf)); memset(path, 0, sizeof(path));
 
     /* Do not crash on NULL buffer pointer */
-    err = mciSendString("sysinfo waveaudio quantity open", NULL, 0, NULL);
+    err = mciSendStringA("sysinfo waveaudio quantity open", NULL, 0, NULL);
     ok(err==MCIERR_PARAM_OVERFLOW,"mci sysinfo without buffer returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL);
+    err = mciSendStringA("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL);
     ok(!err,"mci sysinfo waveaudio quantity open returned %s\n", dbg_mcierr(err));
-    if(!err) todo_wine ok(!strcmp(buf,"0"), "sysinfo quantity open expected 0, got: %s, some more tests will fail.\n", buf);
+    if(!err) ok(!strcmp(buf,"0"), "sysinfo quantity open expected 0, got: %s, some more tests will fail.\n", buf);
 
-    ok_snd = waveOutGetNumDevs() ? 0 : MCIERR_HARDWARE;
-    err = mciSendString("sound NoSuchSoundDefined wait", NULL, 0, NULL);
-    todo_wine ok(err==ok_snd,"mci sound NoSuchSoundDefined returned %s\n", dbg_mcierr(err));
+    /* Who knows why some MS machines pass all tests but return MCIERR_HARDWARE here? */
+    /* Wine returns MCIERR_HARDWARE when no default sound is found in win.ini or the registry. */
+    err = mciSendStringA("sound NoSuchSoundDefined wait", NULL, 0, NULL);
+    ok(err==ok_snd || err==MCIERR_HARDWARE, "mci sound NoSuchSoundDefined returned %s\n", dbg_mcierr(err));
 
-    err = mciSendString("sound SystemExclamation notify wait", NULL, 0, hwnd);
-    todo_wine ok(err==ok_snd,"mci sound SystemExclamation returned %s\n", dbg_mcierr(err));
+    err = mciSendStringA("sound SystemExclamation notify wait", NULL, 0, hwnd);
+    ok(err==ok_snd || err==MCIERR_HARDWARE, "mci sound SystemExclamation returned %s\n", dbg_mcierr(err));
     test_notification(hwnd, "sound notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
 
-    buf[0]=0;
-    err = mciSendString("sysinfo waveaudio name 1 open", buf, sizeof(buf), NULL);
-    todo_wine ok(err==MCIERR_OUTOFRANGE,"sysinfo waveaudio name 1 returned %s\n", dbg_mcierr(err));
+    Sleep(16); /* time to auto-close makes sysinfo below return expected error */
+    err = mciSendStringA("sysinfo waveaudio notify name 1 open", buf, sizeof(buf), hwnd);
+    ok(err==MCIERR_OUTOFRANGE,"sysinfo waveaudio name 1 returned %s\n", dbg_mcierr(err));
     if(!err) trace("sysinfo dangling open alias: %s\n", buf);
+    test_notification(hwnd, "sysinfo name outofrange\n", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
 
-    err = mciSendString("play no-such-file-exists.wav notify", buf, sizeof(buf), NULL);
-    if(err==MCIERR_FILE_NOT_FOUND) { /* a Wine detector */
-        /* Unsupported auto-open leaves the file open, preventing clean-up */
-        skip("Skipping auto-open tests in Wine\n");
-        return;
-    }
+    err = mciSendStringA("play no-such-file-exists.wav notify", buf, sizeof(buf), NULL);
+    todo_wine ok(err==MCIERR_NOTIFY_ON_AUTO_OPEN,"mci auto-open notify returned %s\n", dbg_mcierr(err));
+    /* FILE_NOT_FOUND in Wine because auto-open fails before testing the notify flag */
 
-    err = mciSendString("play tempfile.wav notify", buf, sizeof(buf), hwnd);
-    todo_wine ok(err==MCIERR_NOTIFY_ON_AUTO_OPEN,"mci auto-open play notify returned %s\n", dbg_mcierr(err));
+    test_notification(hwnd, "-prior to auto-open-", 0);
+
+    err = mciSendStringA("play tempfile.wav notify", buf, sizeof(buf), hwnd);
+    if(ok_saved==MCIERR_FILE_NOT_FOUND) todo_wine /* same as above */
+    ok(err==MCIERR_NOTIFY_ON_AUTO_OPEN,"mci auto-open play notify returned %s\n", dbg_mcierr(err));
+    else
+    ok(err==MCIERR_NOTIFY_ON_AUTO_OPEN,"mci auto-open play notify returned %s\n", dbg_mcierr(err));
 
     if(err) /* FIXME: don't open twice yet, it confuses Wine. */
-    err = mciSendString("play tempfile.wav", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("play tempfile.wav", buf, sizeof(buf), hwnd);
     ok(err==ok_saved,"mci auto-open play returned %s\n", dbg_mcierr(err));
 
     if(err==MCIERR_FILE_NOT_FOUND) {
@@ -749,36 +1179,47 @@ static void test_AutoOpenWAVE(HWND hwnd)
         return;
     }
 
-    buf[0]=0;
-    err = mciSendString("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL);
+    err = mciSendStringA("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL);
     ok(!err,"mci sysinfo waveaudio quantity after auto-open returned %s\n", dbg_mcierr(err));
-    if(!err) todo_wine ok(!strcmp(buf,"1"), "sysinfo quantity open expected 1, got: %s\n", buf);
+    if(!err) ok(!strcmp(buf,"1"), "sysinfo quantity open expected 1, got: %s\n", buf);
 
-    buf[0]=0;
-    err = mciSendString("sysinfo waveaudio name 1 open", buf, sizeof(buf), NULL);
-    todo_wine ok(!err,"mci sysinfo waveaudio name after auto-open returned %s\n", dbg_mcierr(err));
+    parm.sys.lpstrReturn = (LPSTR)&intbuf[1];
+    parm.sys.dwRetSize = 2*sizeof(DWORD); /* only one DWORD is used */
+    parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO;
+    err = mciSendCommandA(0, MCI_SYSINFO, MCI_SYSINFO_QUANTITY | MCI_SYSINFO_OPEN, (DWORD_PTR)&parm);
+    ok(!err, "mciCommand sysinfo waveaudio open notify returned %s\n", dbg_mcierr(err));
+    if(!err) ok(atoi(buf)==intbuf[1],"sysinfo waveaudio quantity open string and command differ\n");
+
+    err = mciSendStringA("sysinfo waveaudio name 1 open notify", buf, sizeof(buf), hwnd);
+    ok(!err,"mci sysinfo waveaudio name after auto-open returned %s\n", dbg_mcierr(err));
     /* This is the alias, not necessarily a file name. */
     if(!err) ok(!strcmp(buf,"tempfile.wav"), "sysinfo name 1 open: %s\n", buf);
+    test_notification(hwnd, "sysinfo name notify\n", MCI_NOTIFY_SUCCESSFUL);
+
+    err = mciGetDeviceIDA("tempfile.wav");
+    ok(err == 1, "mciGetDeviceIDA tempfile.wav returned %u, expected 1\n", err);
 
     /* Save the full pathname to the file. */
-    err = mciSendString("info tempfile.wav file", path, sizeof(path), NULL);
+    err = mciSendStringA("info tempfile.wav file", path, sizeof(path), NULL);
     ok(!err,"mci info tempfile.wav file returned %s\n", dbg_mcierr(err));
     if(err) strcpy(path,"tempfile.wav");
 
-    err = mciSendString("status tempfile.wav mode", NULL, 0, hwnd);
+    err = mciSendStringA("status tempfile.wav mode", NULL, 0, hwnd);
     ok(!err,"mci status tempfile.wav mode without buffer returned %s\n", dbg_mcierr(err));
 
     sprintf(command,"status \"%s\" mode",path);
-    err = mciSendString(command, buf, sizeof(buf), hwnd);
+    err = mciSendStringA(command, buf, sizeof(buf), hwnd);
     ok(!err,"mci status \"%s\" mode returned %s\n", path, dbg_mcierr(err));
 
-    buf[0]=0;
-    err = mciSendString("status tempfile.wav mode", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status tempfile.wav mode", buf, sizeof(buf), hwnd);
     ok(!err,"mci status tempfile.wav mode returned %s\n", dbg_mcierr(err));
     if(!err) ok(!strcmp(buf,"playing"), "mci auto-open status mode, got: %s\n", buf);
 
-    err = mciSendString("open tempfile.wav", buf, sizeof(buf), NULL);
-    todo_wine ok(err==MCIERR_DEVICE_OPEN, "mci open from auto-open returned %s\n", dbg_mcierr(err));
+    err = mciSendStringA("open tempfile.wav", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_DEVICE_OPEN, "mci open from auto-open returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("open foo.wav alias tempfile.wav", buf, sizeof(buf), NULL);
+    ok(err==MCIERR_DUPLICATE_ALIAS, "mci open re-using alias returned %s\n", dbg_mcierr(err));
 
     /* w2k/xp and Wine differ. While the device is busy playing, it is
      * regularly open and accessible via the filename: subsequent
@@ -791,71 +1232,134 @@ static void test_AutoOpenWAVE(HWND hwnd)
      * MCIERR_NOTIFY_ON_AUTO_OPEN and thus don't abort the original
      * command.
      */
-    err = mciSendString("status tempfile.wav mode notify", buf, sizeof(buf), hwnd);
+    err = mciSendStringA("status tempfile.wav mode notify", buf, sizeof(buf), hwnd);
     todo_wine ok(err==MCIERR_NOTIFY_ON_AUTO_OPEN, "mci status auto-open notify returned %s\n", dbg_mcierr(err));
     if(!err) {
         trace("Wine style MCI auto-close upon notification\n");
 
         /* "playing" because auto-close comes after the status call. */
-        todo_wine ok(!strcmp(buf,"playing"), "mci auto-open status mode notify, got: %s\n", buf);
+        ok(!strcmp(buf,"playing"), "mci auto-open status mode notify, got: %s\n", buf);
         /* fixme:winmm:MMDRV_Exit Closing while ll-driver open
          *  is explained by failure to auto-close a device. */
         test_notification(hwnd,"status notify",MCI_NOTIFY_SUCCESSFUL);
         /* MCI received NOTIFY_SUPERSEDED and auto-closed the device. */
+
+        /* Until this is implemented, force closing the device */
+        err = mciSendStringA("close tempfile.wav", NULL, 0, hwnd);
+        ok(!err,"mci auto-still-open stop returned %s\n", dbg_mcierr(err));
         Sleep(16);
         test_notification(hwnd,"auto-open",0);
     } else if(err==MCIERR_NOTIFY_ON_AUTO_OPEN) { /* MS style */
         trace("MS style MCI auto-open forbids notification\n");
 
-        err = mciSendString("pause tempfile.wav", NULL, 0, hwnd);
+        err = mciSendStringA("pause tempfile.wav", NULL, 0, hwnd);
         ok(!err,"mci auto-still-open pause returned %s\n", dbg_mcierr(err));
 
-        err = mciSendString("status tempfile.wav mode", buf, sizeof(buf), hwnd);
+        err = mciSendStringA("status tempfile.wav mode", buf, sizeof(buf), hwnd);
         ok(!err,"mci status mode returned %s\n", dbg_mcierr(err));
         if(!err) ok(!strcmp(buf,"paused"), "mci auto-open status mode, got: %s\n", buf);
 
         /* Auto-close */
-        err = mciSendString("stop tempfile.wav", NULL, 0, hwnd);
+        err = mciSendStringA("stop tempfile.wav wait", NULL, 0, hwnd);
         ok(!err,"mci auto-still-open stop returned %s\n", dbg_mcierr(err));
         Sleep(16); /* makes sysinfo quantity open below succeed */
     }
 
-    err = mciSendString("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL);
+    err = mciSendStringA("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL);
     ok(!err,"mci sysinfo waveaudio quantity open after close returned %s\n", dbg_mcierr(err));
-    if(!err) todo_wine ok(!strcmp(buf,"0"), "sysinfo quantity open expected 0 after auto-close, got: %s\n", buf);
+    if(!err) ok(!strcmp(buf,"0"), "sysinfo quantity open expected 0 after auto-close, got: %s\n", buf);
 
     /* w95-WinME (not w2k/XP) switch to C:\ after auto-playing once.  Prevent
      * MCIERR_FILE_NOT_FOUND by using the full path name from the Info file command.
      */
     sprintf(command,"status \"%s\" mode wait",path);
-    err = mciSendString(command, buf, sizeof(buf), hwnd);
+    err = mciSendStringA(command, buf, sizeof(buf), hwnd);
     ok(!err,"mci re-auto-open status mode returned %s\n", dbg_mcierr(err));
     if(!err) ok(!strcmp(buf,"stopped"), "mci re-auto-open status mode, got: %s\n", buf);
 
-    err = mciSendString("capability waveaudio device type", buf, sizeof(buf), hwnd);
+    /* This uses auto-open as well. */
+    err = mciSendStringA("capability waveaudio outputs", buf, sizeof(buf), NULL);
+    ok(!err,"mci capability waveaudio outputs returned %s\n", dbg_mcierr(err));
+    /* Wine with no sound selected in winecfg's audio tab fails this test. */
+    if(!err) ok(atoi(buf)==ndevs,"Expected %d audio outputs, got %s\n", ndevs, buf);
+
+    err = mciSendStringA("capability waveaudio device type", buf, sizeof(buf), hwnd);
     ok(!err,"mci capability device type returned %s\n", dbg_mcierr(err));
     if(!err) ok(!strcmp(buf,"waveaudio"), "mci capability device type response: %s\n", buf);
 
     /* waveaudio forbids Pause without Play. */
     sprintf(command,"pause \"%s\"",path);
-    err = mciSendString(command, NULL, 0, hwnd);
+    err = mciSendStringA(command, NULL, 0, hwnd);
     ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci auto-open pause returned %s\n", dbg_mcierr(err));
+
+    ok(0xDEADF00D==intbuf[0] && 0xABADCAFE==intbuf[2],"DWORD buffer corruption\n");
+}
+
+static void test_playWaveTypeMpegvideo(void)
+{
+    MCIERROR err;
+    MCIDEVICEID wDeviceID;
+    MCI_PLAY_PARMS play_parm;
+    MCI_STATUS_PARMS status_parm;
+    char buf[1024];
+    memset(buf, 0, sizeof(buf));
+
+    err = mciSendStringA("open tempfile.wav type MPEGVideo alias mysound", NULL, 0, NULL);
+    ok(err==ok_saved,"mci open tempfile.wav type MPEGVideo returned %s\n", dbg_mcierr(err));
+    if(err) {
+        skip("Cannot open tempfile.wav type MPEGVideo for playing (%s), skipping\n", dbg_mcierr(err));
+        return;
+    }
+
+    wDeviceID = mciGetDeviceIDA("mysound");
+    ok(wDeviceID == 1, "mciGetDeviceIDA mysound returned %u, expected 1\n", wDeviceID);
+
+    err = mciSendCommandA(wDeviceID, MCI_PLAY, 0, (DWORD_PTR)&play_parm);
+    ok(!err,"mciCommand play returned %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status mysound mode", buf, sizeof(buf), NULL);
+    ok(!err,"mci status mode returned %s\n", dbg_mcierr(err));
+    ok(!strcmp(buf,"playing"), "mci status mode: %s\n", buf);
+
+    status_parm.dwItem = MCI_STATUS_MODE;
+    err = mciSendCommandA(wDeviceID, MCI_STATUS,
+                          MCI_STATUS_ITEM,
+                          (DWORD_PTR)&status_parm);
+    ok(!err,"mciCommand status mode returned %s\n", dbg_mcierr(err));
+    ok(status_parm.dwReturn == MCI_MODE_PLAY,
+       "mciCommand status mode: %u\n", (DWORD)status_parm.dwReturn);
+
+    err = mciSendStringA("close mysound", NULL, 0, NULL);
+    ok(!err,"mci close returned %s\n", dbg_mcierr(err));
 }
 
 START_TEST(mci)
 {
+    char curdir[MAX_PATH], tmpdir[MAX_PATH];
     MCIERROR err;
     HWND hwnd;
+
+    GetCurrentDirectoryA(MAX_PATH, curdir);
+    GetTempPathA(MAX_PATH, tmpdir);
+    SetCurrentDirectoryA(tmpdir);
+
     hwnd = CreateWindowExA(0, "static", "winmm test", WS_POPUP, 0,0,100,100,
                            0, 0, 0, NULL);
+    test_mciParser(hwnd);
     test_openCloseWAVE(hwnd);
     test_recordWAVE(hwnd);
-    test_playWAVE(hwnd);
-    test_asyncWAVE(hwnd);
-    test_AutoOpenWAVE(hwnd);
+    if(waveOutGetNumDevs()){
+        test_playWAVE(hwnd);
+        test_asyncWAVE(hwnd);
+        test_AutoOpenWAVE(hwnd);
+        test_playWaveTypeMpegvideo();
+    }else
+        skip("No output devices available, skipping all output tests\n");
     /* Win9X hangs when exiting with something still open. */
-    err = mciSendString("close all", NULL, 0, hwnd);
-    todo_wine ok(!err,"final close all returned %s\n", dbg_mcierr(err));
-    ok(DeleteFile("tempfile.wav")||ok_saved,"Delete tempfile.wav (cause auto-open?)\n");
+    err = mciSendStringA("close all", NULL, 0, hwnd);
+    ok(!err,"final close all returned %s\n", dbg_mcierr(err));
+    ok(DeleteFileA("tempfile.wav") || ok_saved, "Delete tempfile.wav (cause auto-open?)\n");
     DestroyWindow(hwnd);
+
+    SetCurrentDirectoryA(curdir);
 }
diff --git a/rostests/winetests/winmm/mcicda.c b/rostests/winetests/winmm/mcicda.c
new file mode 100644 (file)
index 0000000..f6e126d
--- /dev/null
@@ -0,0 +1,612 @@
+/*
+ * Test MCI CD-ROM access
+ *
+ * Copyright 2010 Jörg Höhle
+ *
+ * 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 <stdio.h>
+#include "windows.h"
+#include "mmsystem.h"
+#include "wine/test.h"
+
+typedef union {
+      MCI_STATUS_PARMS     status;
+      MCI_GETDEVCAPS_PARMS caps;
+      MCI_OPEN_PARMSA      open;
+      MCI_PLAY_PARMS       play;
+      MCI_SEEK_PARMS       seek;
+      MCI_SAVE_PARMSA      save;
+      MCI_GENERIC_PARMS    gen;
+    } MCI_PARMS_UNION;
+
+extern const char* dbg_mcierr(MCIERROR err); /* from mci.c */
+
+static BOOL spurious_message(LPMSG msg)
+{
+  /* WM_DEVICECHANGE 0x0219 appears randomly */
+  if(msg->message != MM_MCINOTIFY) {
+    trace("skipping spurious message %04x\n",msg->message);
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/* A single ok() in each code path allows us to prefix this with todo_wine */
+#define test_notification(hwnd, command, type) test_notification_dbg(hwnd, command, type, __LINE__)
+static void test_notification_dbg(HWND hwnd, const char* command, WPARAM type, int line)
+{   /* Use type 0 as meaning no message */
+    MSG msg;
+    BOOL seen;
+    do { seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE); }
+    while(seen && spurious_message(&msg));
+    if(type && !seen) {
+      /* We observe transient delayed notification, mostly on native.
+       * Notification is not always present right when mciSend returns. */
+      trace_(__FILE__,line)("Waiting for delayed notification from %s\n", command);
+      MsgWaitForMultipleObjects(0, NULL, FALSE, 3000, QS_POSTMESSAGE);
+      seen = PeekMessageA(&msg, hwnd, MM_MCINOTIFY, MM_MCINOTIFY, PM_REMOVE);
+    }
+    if(!seen)
+      ok_(__FILE__,line)(type==0, "Expect message %04lx from %s\n", type, command);
+    else if(msg.hwnd != hwnd)
+        ok_(__FILE__,line)(msg.hwnd == hwnd, "Didn't get the handle to our test window\n");
+    else if(msg.message != MM_MCINOTIFY)
+        ok_(__FILE__,line)(msg.message == MM_MCINOTIFY, "got %04x instead of MM_MCINOTIFY from command %s\n", msg.message, command);
+    else ok_(__FILE__,line)(msg.wParam == type, "got %04lx instead of MCI_NOTIFY_xyz %04lx from command %s\n", msg.wParam, type, command);
+}
+
+#define CDFRAMES_PERSEC                 75
+static DWORD MSF_Add(DWORD d1, DWORD d2)
+{
+    WORD c, m, s, f;
+    f = MCI_MSF_FRAME(d1)  + MCI_MSF_FRAME(d2);
+    c = f / CDFRAMES_PERSEC;
+    f = f % CDFRAMES_PERSEC;
+    s = MCI_MSF_SECOND(d1) + MCI_MSF_SECOND(d2) + c;
+    c = s / 60;
+    s = s % 60;
+    m = MCI_MSF_MINUTE(d1) + MCI_MSF_MINUTE(d2) + c; /* may be > 60 */
+    return MCI_MAKE_MSF(m,s,f);
+}
+
+static MCIERROR ok_open = 0; /* MCIERR_CANNOT_LOAD_DRIVER */
+
+/* TODO show that shareable flag is not what Wine implements. */
+
+static void test_play(HWND hwnd)
+{
+    MCIDEVICEID wDeviceID;
+    MCI_PARMS_UNION parm;
+    MCIERROR err, ok_hw;
+    DWORD numtracks, track, duration;
+    DWORD factor = winetest_interactive ? 3 : 1;
+    char buf[1024];
+    memset(buf, 0, sizeof(buf));
+    parm.gen.dwCallback = (DWORD_PTR)hwnd; /* once to rule them all */
+
+    err = mciSendStringA("open cdaudio alias c notify shareable", buf, sizeof(buf), hwnd);
+    ok(!err || err == MCIERR_CANNOT_LOAD_DRIVER || err == MCIERR_MUST_USE_SHAREABLE,
+       "mci open cdaudio notify returned %s\n", dbg_mcierr(err));
+    ok_open = err;
+    test_notification(hwnd, "open alias notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
+    /* Native returns MUST_USE_SHAREABLE when there's trouble with the hardware
+     * (e.g. unreadable disk) or when Media Player already has the device open,
+     * yet adding that flag does not help get past this error. */
+
+    if(err) {
+        skip("Cannot open any cdaudio device, %s.\n", dbg_mcierr(err));
+        return;
+    }
+    wDeviceID = atoi(buf);
+    ok(!strcmp(buf,"1"), "mci open deviceId: %s, expected 1\n", buf);
+    /* Win9X-ME may start the MCI and media player upon insertion of a CD. */
+
+    err = mciSendStringA("sysinfo all name 1 open", buf, sizeof(buf), NULL);
+    ok(!err,"sysinfo all name 1 returned %s\n", dbg_mcierr(err));
+    if(!err && wDeviceID != 1) trace("Device '%s' is open.\n", buf);
+
+    err = mciSendStringA("capability c has video notify", buf, sizeof(buf), hwnd);
+    ok(!err, "capability video: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "false"), "capability video is %s\n", buf);
+    test_notification(hwnd, "capability notify", MCI_NOTIFY_SUCCESSFUL);
+
+    err = mciSendStringA("capability c can play", buf, sizeof(buf), hwnd);
+    ok(!err, "capability video: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "true"), "capability play is %s\n", buf);
+
+    err = mciSendStringA("capability c", buf, sizeof(buf), NULL);
+    ok(err == MCIERR_MISSING_PARAMETER, "capability nokeyword: %s\n", dbg_mcierr(err));
+
+    parm.caps.dwItem = 0x4001;
+    parm.caps.dwReturn = 0xFEEDABAD;
+    err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
+    ok(err == MCIERR_UNSUPPORTED_FUNCTION, "GETDEVCAPS %x: %s\n", parm.caps.dwItem, dbg_mcierr(err));
+
+    parm.caps.dwItem = MCI_GETDEVCAPS_DEVICE_TYPE;
+    err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
+    ok(!err, "GETDEVCAPS device type: %s\n", dbg_mcierr(err));
+    if(!err) ok( parm.caps.dwReturn == MCI_DEVTYPE_CD_AUDIO, "getdevcaps device type: %u\n", parm.caps.dwReturn);
+
+    err = mciSendCommandA(wDeviceID, MCI_RECORD, 0, (DWORD_PTR)&parm);
+    ok(err == MCIERR_UNSUPPORTED_FUNCTION, "MCI_RECORD: %s\n", dbg_mcierr(err));
+
+    /* Wine's MCI_MapMsgAtoW crashes on MCI_SAVE without parm->lpfilename */
+    parm.save.lpfilename = "foo";
+    err = mciSendCommandA(wDeviceID, MCI_SAVE, 0, (DWORD_PTR)&parm);
+    ok(err == MCIERR_UNSUPPORTED_FUNCTION, "MCI_SAVE: %s\n", dbg_mcierr(err));
+
+    /* commands from the core set are UNSUPPORTED, others UNRECOGNIZED */
+    err = mciSendCommandA(wDeviceID, MCI_STEP, 0, (DWORD_PTR)&parm);
+    ok(err == MCIERR_UNRECOGNIZED_COMMAND, "MCI_STEP: %s\n", dbg_mcierr(err));
+
+    parm.status.dwItem = MCI_STATUS_TIME_FORMAT;
+    parm.status.dwReturn = 0xFEEDABAD;
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
+    ok(!err, "STATUS time format: %s\n", dbg_mcierr(err));
+    if(!err) ok(parm.status.dwReturn == MCI_FORMAT_MSF, "status time default format: %ld\n", parm.status.dwReturn);
+
+    /* "CD-Audio" */
+    err = mciSendStringA("info c product wait notify", buf, sizeof(buf), hwnd);
+    ok(!err, "info product: %s\n", dbg_mcierr(err));
+    test_notification(hwnd, "info notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
+
+    parm.status.dwItem = MCI_STATUS_MEDIA_PRESENT;
+    parm.status.dwReturn = 0xFEEDABAD;
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
+    ok(err || parm.status.dwReturn == TRUE || parm.status.dwReturn == FALSE,
+       "STATUS media present: %s\n", dbg_mcierr(err));
+
+    if (parm.status.dwReturn != TRUE) {
+        skip("No CD-ROM in drive.\n");
+        return;
+    }
+
+    parm.status.dwItem = MCI_STATUS_MODE;
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
+    ok(!err, "STATUS mode: %s\n", dbg_mcierr(err));
+    switch(parm.status.dwReturn) {
+    case MCI_MODE_NOT_READY:
+        skip("CD-ROM mode not ready (DVD in drive?)\n");
+        return;
+    case MCI_MODE_OPEN: /* should not happen with MEDIA_PRESENT */
+        skip("CD-ROM drive is open\n");
+        /* set door closed may not work. */
+        return;
+    default: /* play/record/seek/pause */
+        ok(parm.status.dwReturn==MCI_MODE_STOP, "STATUS mode is %lx\n", parm.status.dwReturn);
+        /* fall through */
+    case MCI_MODE_STOP: /* normal */
+        break;
+    }
+
+    /* Initial mode is "stopped" with a CD in drive */
+    err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
+    ok(!err, "status mode: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "stopped"), "status mode is initially %s\n", buf);
+
+    err = mciSendStringA("status c ready", buf, sizeof(buf), hwnd);
+    ok(!err, "status ready: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "true"), "status ready with media is %s\n", buf);
+
+    err = mciSendStringA("info c product identity", buf, sizeof(buf), hwnd);
+    ok(!err, "info 2flags: %s\n", dbg_mcierr(err)); /* not MCIERR_FLAGS_NOT_COMPATIBLE */
+    /* Precedence rule p>u>i verified experimentally, not tested here. */
+
+    err = mciSendStringA("info c identity", buf, sizeof(buf), hwnd);
+    ok(!err || err == MCIERR_HARDWARE, "info identity: %s\n", dbg_mcierr(err));
+    /* a blank disk causes MCIERR_HARDWARE and other commands to fail likewise. */
+    ok_hw = err;
+
+    err = mciSendStringA("info c upc", buf, sizeof(buf), hwnd);
+    ok(err == ok_hw || err == MCIERR_NO_IDENTITY, "info upc: %s\n", dbg_mcierr(err));
+
+    parm.status.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
+    parm.status.dwReturn = 0;
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
+    ok(err == ok_hw, "STATUS number of tracks: %s\n", dbg_mcierr(err));
+    numtracks = parm.status.dwReturn;
+    /* cf. MAXIMUM_NUMBER_TRACKS */
+    ok(0 < numtracks && numtracks <= 99, "number of tracks=%ld\n", parm.status.dwReturn);
+
+    err = mciSendStringA("status c length", buf, sizeof(buf), hwnd);
+    ok(err == ok_hw, "status length: %s\n", dbg_mcierr(err));
+    if(!err) trace("CD length %s\n", buf);
+
+    if(err) { /* MCIERR_HARDWARE when given a blank disk */
+        skip("status length %s (blank disk?)\n", dbg_mcierr(err));
+        return;
+    }
+
+    /* Linux leaves the drive at some random position,
+     * native initialises to the start position below. */
+    err = mciSendStringA("status c position", buf, sizeof(buf), hwnd);
+    ok(!err, "status position: %s\n", dbg_mcierr(err));
+    if(!err) todo_wine ok(!strcmp(buf, "00:02:00") || !strcmp(buf, "00:02:33") || !strcmp(buf, "00:03:00"),
+                "status position initially %s\n", buf);
+    /* 2 seconds is the initial position even with data tracks. */
+
+    err = mciSendStringA("status c position start notify", buf, sizeof(buf), hwnd);
+    ok(err == ok_hw, "status position start: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "00:02:00") || !strcmp(buf, "00:02:33") || !strcmp(buf, "00:03:00"),
+                "status position start %s\n", buf);
+    test_notification(hwnd, "status notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
+
+    err = mciSendStringA("status c position start track 1 notify", buf, sizeof(buf), hwnd);
+    ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "status position start: %s\n", dbg_mcierr(err));
+    test_notification(hwnd, "status 2flags", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
+
+    err = mciSendStringA("play c from 00:02:00 to 00:01:00 notify", buf, sizeof(buf), hwnd);
+    ok(err == MCIERR_OUTOFRANGE, "play 2s to 1s: %s\n", dbg_mcierr(err));
+    test_notification(hwnd, "play 2s to 1s", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
+
+    err = mciSendStringA("resume c", buf, sizeof(buf), hwnd);
+    ok(err == MCIERR_HARDWARE || /* Win9x */ err == MCIERR_UNSUPPORTED_FUNCTION,
+       "resume without play: %s\n", dbg_mcierr(err)); /* not NONAPPLICABLE_FUNCTION */
+    /* vmware with a .iso (data-only) yields no error on NT/w2k */
+
+    err = mciSendStringA("seek c wait", buf, sizeof(buf), hwnd);
+    ok(err == MCIERR_MISSING_PARAMETER, "seek noflag: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("seek c to start to end", buf, sizeof(buf), hwnd);
+    ok(err == MCIERR_FLAGS_NOT_COMPATIBLE || broken(!err), "seek to start+end: %s\n", dbg_mcierr(err));
+    /* Win9x only errors out with Seek to start to <position> */
+
+    /* set Wine to a defined position before play */
+    err = mciSendStringA("seek c to start notify", buf, sizeof(buf), hwnd);
+    ok(!err, "seek to start: %s\n", dbg_mcierr(err));
+    test_notification(hwnd, "seek to start", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
+    /* Win9X Status position / current track then sometimes report the end position / track! */
+
+    err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
+    ok(!err, "status mode: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "stopped"), "status mode after seek is %s\n", buf);
+
+    /* MCICDA ignores MCI_SET_VIDEO */
+    err = mciSendStringA("set c video on", buf, sizeof(buf), hwnd);
+    ok(!err, "set video: %s\n", dbg_mcierr(err));
+
+    /* One xp machine ignored SET_AUDIO, one w2k and one w7 machine honoured it
+     * and simultaneously toggled the mute button in the mixer control panel.
+     * Or does it only depend on the HW, not the OS?
+     * Some vmware machines return MCIERR_HARDWARE. */
+    err = mciSendStringA("set c audio all on", buf, sizeof(buf), hwnd);
+    ok(!err || err == MCIERR_HARDWARE, "set audio: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("set c time format ms", buf, sizeof(buf), hwnd);
+    ok(!err, "set time format ms: %s\n", dbg_mcierr(err));
+
+    memset(buf, 0, sizeof(buf));
+    err = mciSendStringA("status c position start", buf, sizeof(buf), hwnd);
+    ok(!err, "status position start (ms): %s\n", dbg_mcierr(err));
+    duration = atoi(buf);
+    if(!err) ok(duration > 2000, "status position initially %sms\n", buf);
+    /* 00:02:00 corresponds to 2001 ms, 02:33 -> 2441 etc. */
+
+    err = mciSendStringA("status c position start track 1", buf, sizeof(buf), hwnd);
+    ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "status position start+track: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status c notify wait", buf, sizeof(buf), hwnd);
+    ok(err == MCIERR_MISSING_PARAMETER, "status noflag: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status c length track 1", buf, sizeof(buf), hwnd);
+    ok(!err, "status length (ms): %s\n", dbg_mcierr(err));
+    if(!err) {
+        trace("track #1 length %sms\n", buf);
+        duration = atoi(buf);
+    } else duration = 2001; /* for the position test below */
+
+    if (0) { /* causes some native systems to return Seek and Play with MCIERR_HARDWARE */
+        /* depending on capability can eject only? */
+        err = mciSendStringA("set c door closed notify", buf, sizeof(buf), hwnd);
+        ok(!err, "set door closed: %s\n", dbg_mcierr(err));
+        test_notification(hwnd, "door closed", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
+    }
+    /* Changing the disk while the MCI device is open causes the Status
+     * command to report stale data.  Native obviously caches the TOC. */
+
+    /* status type track is localised, strcmp("audio|other") may fail. */
+    parm.status.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
+    parm.status.dwTrack = 1;
+    parm.status.dwReturn = 0xFEEDABAD;
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
+    ok(!err, "STATUS type track 1: %s\n", dbg_mcierr(err));
+    ok(parm.status.dwReturn==MCI_CDA_TRACK_OTHER || parm.status.dwReturn==MCI_CDA_TRACK_AUDIO,
+       "unknown track type %lx\n", parm.status.dwReturn);
+
+    if (parm.status.dwReturn == MCI_CDA_TRACK_OTHER) {
+        /* Find an audio track */
+        parm.status.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
+        parm.status.dwTrack = numtracks;
+        parm.status.dwReturn = 0xFEEDABAD;
+        err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
+        ok(!err, "STATUS type track %u: %s\n", numtracks, dbg_mcierr(err));
+        ok(parm.status.dwReturn == MCI_CDA_TRACK_OTHER || parm.status.dwReturn == MCI_CDA_TRACK_AUDIO,
+           "unknown track type %lx\n", parm.status.dwReturn);
+        track = (!err && parm.status.dwReturn == MCI_CDA_TRACK_AUDIO) ? numtracks : 0;
+
+        /* Seek to start (above) skips over data tracks
+         * In case of a data only CD, it seeks to the end of disk, however
+         * another Status position a few seconds later yields MCIERR_HARDWARE. */
+        parm.status.dwItem = MCI_STATUS_POSITION;
+        parm.status.dwReturn = 2000;
+        err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
+        ok(!err || broken(err == MCIERR_HARDWARE), "STATUS position: %s\n", dbg_mcierr(err));
+
+        if(!err && track) ok(parm.status.dwReturn > duration,
+            "Seek did not skip data tracks, position %lums\n", parm.status.dwReturn);
+        /* dwReturn > start + length(#1) may fail because of small position report fluctuation.
+         * On some native systems, status position fluctuates around the target position;
+         * Successive calls return varying positions! */
+
+        err = mciSendStringA("set c time format msf", buf, sizeof(buf), hwnd);
+        ok(!err, "set time format msf: %s\n", dbg_mcierr(err));
+
+        parm.status.dwItem = MCI_STATUS_LENGTH;
+        parm.status.dwTrack = 1;
+        parm.status.dwReturn = 0xFEEDABAD;
+        err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
+        ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
+        duration = parm.status.dwReturn;
+        trace("track #1 length: %02um:%02us:%02uframes\n",
+              MCI_MSF_MINUTE(duration), MCI_MSF_SECOND(duration), MCI_MSF_FRAME(duration));
+        ok(duration>>24==0, "CD length high bits %08X\n", duration);
+
+        /* TODO only with mixed CDs? */
+        /* play track 1 to length silently works with data tracks */
+        parm.play.dwFrom = MCI_MAKE_MSF(0,2,0);
+        parm.play.dwTo = duration; /* omitting 2 seconds from end */
+        err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO, (DWORD_PTR)&parm);
+        ok(!err, "PLAY data to %08X: %s\n", duration, dbg_mcierr(err));
+
+        Sleep(1500*factor); /* Time to spin up, hopefully less than track length */
+
+        err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
+        ok(!err, "status mode: %s\n", dbg_mcierr(err));
+        if(!err) ok(!strcmp(buf, "stopped"), "status mode on data is %s\n", buf);
+    } else if (parm.status.dwReturn == MCI_CDA_TRACK_AUDIO) {
+        skip("Got no mixed data+audio CD.\n");
+        track = 1;
+    } else track = 0;
+
+    if (!track) {
+        skip("Found no audio track.\n");
+        return;
+    }
+
+    err = mciSendStringA("set c time format msf", buf, sizeof(buf), hwnd);
+    ok(!err, "set time format msf: %s\n", dbg_mcierr(err));
+
+    parm.status.dwItem = MCI_STATUS_LENGTH;
+    parm.status.dwTrack = numtracks;
+    parm.status.dwReturn = 0xFEEDABAD;
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
+    ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
+    duration = parm.status.dwReturn;
+    trace("last track length: %02um:%02us:%02uframes\n",
+          MCI_MSF_MINUTE(duration), MCI_MSF_SECOND(duration), MCI_MSF_FRAME(duration));
+    ok(duration>>24==0, "CD length high bits %08X\n", duration);
+
+    parm.status.dwItem = MCI_STATUS_POSITION;
+    /* dwTrack is still set */
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
+    ok(!err, "STATUS position start track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
+    trace("last track position: %02um:%02us:%02uframes\n",
+          MCI_MSF_MINUTE(parm.status.dwReturn), MCI_MSF_SECOND(parm.status.dwReturn), MCI_MSF_FRAME(parm.status.dwReturn));
+
+    /* Seek to position + length always works, esp.
+     * for the last track it's NOT the position of the lead-out. */
+    parm.seek.dwTo = MSF_Add(parm.status.dwReturn, duration);
+    err = mciSendCommandA(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm);
+    ok(!err, "SEEK to %08X position last + length: %s\n", parm.seek.dwTo, dbg_mcierr(err));
+
+    parm.seek.dwTo = MSF_Add(parm.seek.dwTo, MCI_MAKE_MSF(0,0,1));
+    err = mciSendCommandA(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm);
+    ok(err == MCIERR_OUTOFRANGE, "SEEK past %08X position last + length: %s\n", parm.seek.dwTo, dbg_mcierr(err));
+
+    err = mciSendStringA("set c time format tmsf", buf, sizeof(buf), hwnd);
+    ok(!err, "set time format tmsf: %s\n", dbg_mcierr(err));
+
+    parm.play.dwFrom = track;
+    err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_NOTIFY, (DWORD_PTR)&parm);
+    ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err));
+
+    if(err) {
+        skip("Cannot manage to play track %u.\n", track);
+        return;
+    }
+
+    Sleep(1800*factor); /* Time to spin up, hopefully less than track length */
+
+    err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
+    ok(!err, "status mode: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "playing"), "status mode during play is %s\n", buf);
+
+    err = mciSendStringA("pause c", buf, sizeof(buf), hwnd);
+    ok(!err, "pause: %s\n", dbg_mcierr(err));
+
+    test_notification(hwnd, "pause should abort notification", MCI_NOTIFY_ABORTED);
+
+    /* Native returns stopped when paused,
+     * yet the Stop command is different as it would disallow Resume. */
+    err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
+    ok(!err, "status mode: %s\n", dbg_mcierr(err));
+    if(!err) todo_wine ok(!strcmp(buf, "stopped"), "status mode while paused is %s\n", buf);
+
+    err = mciSendCommandA(wDeviceID, MCI_RESUME, 0, 0);
+    ok(!err || /* Win9x */ err == MCIERR_UNSUPPORTED_FUNCTION,
+       "RESUME without parms: %s\n", dbg_mcierr(err));
+
+    Sleep(1300*factor);
+
+    /* Native continues to play without interruption */
+    err = mciSendCommandA(wDeviceID, MCI_PLAY, 0, 0);
+    todo_wine ok(!err, "PLAY without parms: %s\n", dbg_mcierr(err));
+
+    parm.play.dwFrom = MCI_MAKE_TMSF(numtracks,0,1,0);
+    parm.play.dwTo = 1;
+    err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO, (DWORD_PTR)&parm);
+    ok(err == MCIERR_OUTOFRANGE, "PLAY: %s\n", dbg_mcierr(err));
+
+    err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
+    ok(!err, "status mode: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "playing"), "status mode after play is %s\n", buf);
+
+    err = mciSendCommandA(wDeviceID, MCI_STOP, MCI_NOTIFY, (DWORD_PTR)&parm);
+    ok(!err, "STOP notify: %s\n", dbg_mcierr(err));
+    test_notification(hwnd, "STOP notify", MCI_NOTIFY_SUCCESSFUL);
+    test_notification(hwnd, "STOP #1", 0);
+
+    parm.play.dwFrom = track;
+    err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_NOTIFY, (DWORD_PTR)&parm);
+    ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err));
+
+    Sleep(1600*factor);
+
+    parm.seek.dwTo = 1; /* not <track>, to test position below */
+    err = mciSendCommandA(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm);
+    ok(!err, "SEEK to %u notify: %s\n", track, dbg_mcierr(err));
+    /* Note that native's Status position / current track may move the head
+     * and reflect the new position only seconds after issuing the command. */
+
+    /* Seek stops */
+    err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
+    ok(!err, "status mode: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "stopped"), "status mode after play is %s\n", buf);
+
+    test_notification(hwnd, "Seek aborts Play", MCI_NOTIFY_ABORTED);
+    test_notification(hwnd, "Seek", 0);
+
+    parm.play.dwFrom = track;
+    parm.play.dwTo = MCI_MAKE_TMSF(track,0,0,21); /* 21 frames, subsecond */
+    err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO | MCI_NOTIFY, (DWORD_PTR)&parm);
+    ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err));
+
+    Sleep(2200*factor);
+
+    err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
+    ok(!err, "status mode: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "stopped") || broken(!strcmp(buf, "playing")), "status mode after play is %s\n", buf);
+    if(!err && !strcmp(buf, "playing")) trace("status playing after sleep\n");
+
+    /* Playing to end asynchronously sends no notification! */
+    test_notification(hwnd, "PLAY to end", 0);
+
+    err = mciSendStringA("status c mode notify", buf, sizeof(buf), hwnd);
+    ok(!err, "status mode: %s\n", dbg_mcierr(err));
+    if(!err) ok(!strcmp(buf, "stopped") || broken(!strcmp(buf, "playing")), "status mode after play is %s\n", buf);
+    if(!err && !strcmp(buf, "playing")) trace("status still playing\n");
+    /* Some systems report playing even after Sleep(3900ms) yet the successful
+     * notification tests (not ABORTED) indicates they are finished. */
+
+    test_notification(hwnd, "dangling from PLAY", MCI_NOTIFY_SUPERSEDED);
+    test_notification(hwnd, "status mode", MCI_NOTIFY_SUCCESSFUL);
+
+    err = mciSendStringA("stop c", buf, sizeof(buf), hwnd);
+    ok(!err, "stop: %s\n", dbg_mcierr(err));
+
+    test_notification(hwnd, "PLAY to end", 0);
+
+    /* length as MSF despite set time format TMSF */
+    parm.status.dwItem = MCI_STATUS_LENGTH;
+    parm.status.dwTrack = numtracks;
+    parm.status.dwReturn = 0xFEEDABAD;
+    err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
+    ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
+    ok(duration == parm.status.dwReturn, "length MSF<>TMSF %08lX\n", parm.status.dwReturn);
+
+    /* Play from position start to start+length always works. */
+    /* TODO? also play it using MSF */
+    parm.play.dwFrom = numtracks;
+    parm.play.dwTo = (duration << 8) | numtracks; /* as TMSF */
+    err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO | MCI_NOTIFY, (DWORD_PTR)&parm);
+    ok(!err, "PLAY (TMSF) from %08X to %08X: %s\n", parm.play.dwFrom, parm.play.dwTo, dbg_mcierr(err));
+
+    Sleep(1400*factor);
+
+    err = mciSendStringA("status c current track", buf, sizeof(buf), hwnd);
+    ok(!err, "status track: %s\n", dbg_mcierr(err));
+    if(!err) todo_wine ok(numtracks == atoi(buf), "status current track gave %s, expected %u\n", buf, numtracks);
+    /* fails in Wine because SEEK is independent on IOCTL_CDROM_RAW_READ */
+
+    err = mciSendCommandA(wDeviceID, MCI_STOP, 0, 0);
+    ok(!err, "STOP: %s\n", dbg_mcierr(err));
+    test_notification(hwnd, "STOP aborts", MCI_NOTIFY_ABORTED);
+    test_notification(hwnd, "STOP final", 0);
+}
+
+static void test_openclose(HWND hwnd)
+{
+    MCIDEVICEID wDeviceID;
+    MCI_PARMS_UNION parm;
+    MCIERROR err;
+    char drive[] = {'a',':','\\','X','\0'};
+    if (ok_open == MCIERR_CANNOT_LOAD_DRIVER) {
+        /* todo_wine Every open below should yield this same error. */
+        skip("CD-ROM device likely not installed or disabled.\n");
+        return;
+    }
+
+    /* Bug in native since NT: After OPEN "c" without MCI_OPEN_ALIAS fails with
+     * MCIERR_DEVICE_OPEN, any subsequent OPEN fails with EXTENSION_NOT_FOUND! */
+    parm.open.lpstrAlias = "x"; /* with alias, OPEN "c" behaves normally */
+    parm.open.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_CD_AUDIO;
+    parm.open.lpstrElementName = drive;
+    for ( ; strlen(drive); drive[strlen(drive)-1] = 0)
+    for (drive[0] = 'a'; drive[0] <= 'z'; drive[0]++) {
+        err = mciSendCommandA(0, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID |
+                MCI_OPEN_SHAREABLE | MCI_OPEN_ALIAS, (DWORD_PTR)&parm);
+        ok(!err || err == MCIERR_INVALID_FILE, "OPEN %s type: %s\n", drive, dbg_mcierr(err));
+        /* open X:\ fails in Win9x/NT. Only open X: works everywhere. */
+        if(!err) {
+            wDeviceID = parm.open.wDeviceID;
+            trace("ok with %s\n", drive);
+            err = mciSendCommandA(wDeviceID, MCI_CLOSE, 0, 0);
+            ok(!err,"mciCommand close returned %s\n", dbg_mcierr(err));
+        }
+    }
+    drive[0] = '\\';
+    err = mciSendCommandA(0, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID |
+            MCI_OPEN_SHAREABLE, (DWORD_PTR)&parm);
+    ok(err == MCIERR_INVALID_FILE, "OPEN %s type: %s\n", drive, dbg_mcierr(err));
+    if(!err) mciSendCommandA(parm.open.wDeviceID, MCI_CLOSE, 0, 0);
+
+    if (0) {
+        parm.open.lpstrElementName = (LPCSTR)0xDEADBEEF;
+        err = mciSendCommandA(0, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_OPEN_ELEMENT_ID |
+                MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE, (DWORD_PTR)&parm);
+        todo_wine ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "OPEN elt_ID: %s\n", dbg_mcierr(err));
+        if(!err) mciSendCommandA(parm.open.wDeviceID, MCI_CLOSE, 0, 0);
+    }
+}
+
+START_TEST(mcicda)
+{
+    MCIERROR err;
+    HWND hwnd;
+    hwnd = CreateWindowExA(0, "static", "mcicda test", WS_POPUP, 0,0,100,100,
+                           0, 0, 0, NULL);
+    test_notification(hwnd, "-prior to tests-", 0);
+    test_play(hwnd);
+    test_openclose(hwnd);
+    err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_STOP, 0, 0);
+    todo_wine ok(!err || broken(err == MCIERR_HARDWARE /* blank CD or testbot without CD-ROM */),
+       "STOP all returned %s\n", dbg_mcierr(err));
+    err = mciSendStringA("close all", NULL, 0, hwnd);
+    ok(!err, "final close all returned %s\n", dbg_mcierr(err));
+    test_notification(hwnd, "-tests complete-", 0);
+    DestroyWindow(hwnd);
+}
diff --git a/rostests/winetests/winmm/midi.c b/rostests/winetests/winmm/midi.c
new file mode 100644 (file)
index 0000000..2f20e18
--- /dev/null
@@ -0,0 +1,848 @@
+/*
+ * Test winmm midi
+ *
+ * Copyright 2010 Jörg Höhle
+ *
+ * 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
+ */
+
+#define _WINE
+
+#include <stdio.h>
+#include <stddef.h>
+#include "windows.h"
+#include "mmsystem.h"
+#include "wine/test.h"
+
+extern const char* mmsys_error(MMRESULT error); /* from wave.c */
+
+/* Test in order of increasing probability to hang.
+ * On many UNIX systems, the Timidity softsynth provides
+ * MIDI sequencer services and it is not particularly robust.
+ */
+
+#define MYCBINST 0x4CAFE5A8 /* not used with window or thread callbacks */
+#define WHATEVER 0xFEEDF00D
+
+static BOOL spurious_message(LPMSG msg)
+{
+  /* WM_DEVICECHANGE 0x0219 appears randomly */
+  if(msg->message == WM_DEVICECHANGE) {
+    trace("skipping spurious message %04x\n", msg->message);
+    return TRUE;
+  }
+  return FALSE;
+}
+
+static UINT      cbmsg  = 0;
+static DWORD_PTR cbval1 = WHATEVER;
+static DWORD_PTR cbval2 = 0;
+static DWORD_PTR cbinst = MYCBINST;
+
+static void CALLBACK callback_func(HWAVEOUT hwo, UINT uMsg,
+                                   DWORD_PTR dwInstance,
+                                   DWORD_PTR dwParam1, DWORD_PTR dwParam2)
+{
+    if (winetest_debug>1)
+        trace("Callback! msg=%x %lx %lx\n", uMsg, dwParam1, dwParam2);
+    cbmsg = uMsg;
+    cbval1 = dwParam1;   /* mhdr or 0 */
+    cbval2 = dwParam2;   /* always 0 */
+    cbinst = dwInstance; /* MYCBINST, see midiOut/StreamOpen */
+}
+
+#define test_notification(hwnd, command, m1, p2) test_notification_dbg(hwnd, command, m1, p2, __LINE__)
+static void test_notification_dbg(HWND hwnd, const char* command, UINT m1, DWORD_PTR p2, int line)
+{   /* Use message type 0 as meaning no message */
+    MSG msg;
+    if (hwnd) {
+        /* msg.wParam is hmidiout, msg.lParam is the mhdr (or 0) */
+        BOOL seen;
+        do { seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE); }
+        while(seen && spurious_message(&msg));
+        if (m1 && !seen) {
+          /* We observe transient delayed notification, mostly on native.
+           * Perhaps the OS preempts the player thread after setting MHDR_DONE
+           * or clearing MHDR_INQUEUE, before calling DriverCallback. */
+          DWORD rc;
+          trace_(__FILE__,line)("Waiting for delayed message %x from %s\n", m1, command);
+          SetLastError(0xDEADBEEF);
+          rc = MsgWaitForMultipleObjects(0, NULL, FALSE, 3000, QS_POSTMESSAGE);
+          ok_(__FILE__,line)(rc==WAIT_OBJECT_0, "Wait failed: %04x %d\n", rc, GetLastError());
+          seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE);
+        }
+        if (seen) {
+            trace_(__FILE__,line)("Message %x, wParam=%lx, lParam=%lx from %s\n",
+                  msg.message, msg.wParam, msg.lParam, command);
+            ok_(__FILE__,line)(msg.hwnd==hwnd, "Didn't get the handle to our test window\n");
+            ok_(__FILE__,line)(msg.message==m1 && msg.lParam==p2, "bad message %x/%lx from %s, expect %x/%lx\n", msg.message, msg.lParam, command, m1, p2);
+        }
+        else ok_(__FILE__,line)(m1==0, "Expect message %x from %s\n", m1, command);
+    }
+    else {
+        /* FIXME: MOM_POSITIONCB and MOM_DONE are so close that a queue is needed. */
+        if (cbmsg) {
+            ok_(__FILE__,line)(cbmsg==m1 && cbval1==p2 && cbval2==0, "bad callback %x/%lx/%lx from %s, expect %x/%lx\n", cbmsg, cbval1, cbval2, command, m1, p2);
+            cbmsg = 0; /* Mark as read */
+            cbval1 = cbval2 = WHATEVER;
+            ok_(__FILE__,line)(cbinst==MYCBINST, "callback dwInstance changed to %lx\n", cbinst);
+        }
+        else ok_(__FILE__,line)(m1==0, "Expect callback %x from %s\n", m1, command);
+    }
+}
+
+
+static void test_midiIn_device(UINT udev, HWND hwnd)
+{
+    HMIDIIN hm;
+    MMRESULT rc;
+    MIDIINCAPSA capsA;
+    MIDIHDR mhdr;
+
+    rc = midiInGetDevCapsA(udev, &capsA, sizeof(capsA));
+    ok((MIDIMAPPER==udev) ? (rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*nt,w2k*/)) : rc==0,
+       "midiInGetDevCaps(dev=%d) rc=%s\n", udev, mmsys_error(rc));
+    if (!rc) {
+        /* MIDI IN capsA.dwSupport may contain garbage, absent in old MS-Windows */
+        trace("* %s: manufacturer=%d, product=%d, support=%X\n", capsA.szPname, capsA.wMid, capsA.wPid, capsA.dwSupport);
+    }
+
+    if (hwnd)
+        rc = midiInOpen(&hm, udev, (DWORD_PTR)hwnd, (DWORD_PTR)MYCBINST, CALLBACK_WINDOW);
+    else
+        rc = midiInOpen(&hm, udev, (DWORD_PTR)callback_func, (DWORD_PTR)MYCBINST, CALLBACK_FUNCTION);
+    ok((MIDIMAPPER!=udev) ? rc==0 : (rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*nt,w2k*/)),
+       "midiInOpen(dev=%d) rc=%s\n", udev, mmsys_error(rc));
+    if (rc) return;
+
+    test_notification(hwnd, "midiInOpen", MIM_OPEN, 0);
+
+    memset(&mhdr, 0, sizeof(mhdr));
+    mhdr.dwFlags = MHDR_DONE;
+    mhdr.dwUser = 0x56FA552C;
+    mhdr.dwBufferLength = 70000; /* > 64KB! */
+    mhdr.dwBytesRecorded = 5;
+    mhdr.lpData = HeapAlloc(GetProcessHeap(), 0 , mhdr.dwBufferLength);
+    ok(mhdr.lpData!=NULL, "No %d bytes of memory!\n", mhdr.dwBufferLength);
+    if (mhdr.lpData) {
+        rc = midiInPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset)-1);
+        ok(rc==MMSYSERR_INVALPARAM, "midiInPrepare tiny rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == MHDR_DONE, "dwFlags=%x\n", mhdr.dwFlags);
+
+        mhdr.dwFlags |= MHDR_INQUEUE;
+        rc = midiInPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset));
+        ok(!rc, "midiInPrepare old size rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_INQUEUE|MHDR_DONE)/*w9x*/ ||
+           mhdr.dwFlags == MHDR_PREPARED, "dwFlags=%x\n", mhdr.dwFlags);
+        trace("MIDIHDR flags=%x when unsent\n", mhdr.dwFlags);
+
+        mhdr.dwFlags |= MHDR_INQUEUE|MHDR_DONE;
+        rc = midiInPrepareHeader(hm, &mhdr, sizeof(mhdr));
+        ok(!rc, "midiInPrepare rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_INQUEUE|MHDR_DONE), "dwFlags=%x\n", mhdr.dwFlags);
+
+        mhdr.dwFlags &= ~MHDR_INQUEUE;
+        rc = midiInUnprepareHeader(hm, &mhdr, sizeof(mhdr));
+        ok(!rc, "midiInUnprepare rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == MHDR_DONE, "dwFlags=%x\n", mhdr.dwFlags);
+
+        mhdr.dwFlags &= ~MHDR_DONE;
+        rc = midiInUnprepareHeader(hm, &mhdr, sizeof(mhdr));
+        ok(!rc, "midiInUnprepare rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == 0, "dwFlags=%x\n", mhdr.dwFlags);
+
+        rc = midiInPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset));
+        ok(!rc, "midiInPrepare rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == MHDR_PREPARED, "dwFlags=%x\n", mhdr.dwFlags);
+
+        mhdr.dwFlags |= MHDR_DONE;
+        rc = midiInPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset));
+        ok(!rc, "midiInPrepare rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwBytesRecorded == 5, "BytesRec=%u\n", mhdr.dwBytesRecorded);
+
+        mhdr.dwFlags |= MHDR_DONE;
+        rc = midiInAddBuffer(hm, &mhdr, sizeof(mhdr));
+        ok(!rc, "midiAddBuffer rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_INQUEUE), "dwFlags=%x\n", mhdr.dwFlags);
+
+        /* w95 does not set dwBytesRecorded=0 within midiInAddBuffer.  Wine does. */
+        if (mhdr.dwBytesRecorded != 0)
+            trace("dwBytesRecorded %u\n", mhdr.dwBytesRecorded);
+
+        rc = midiInAddBuffer(hm, &mhdr, sizeof(mhdr));
+        ok(rc==MIDIERR_STILLPLAYING, "midiAddBuffer rc=%s\n", mmsys_error(rc));
+
+        rc = midiInPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset));
+        ok(!rc, "midiInPrepare rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_INQUEUE), "dwFlags=%x\n", mhdr.dwFlags);
+    }
+    rc = midiInReset(hm); /* Return any pending buffer */
+    ok(!rc, "midiInReset rc=%s\n", mmsys_error(rc));
+    test_notification(hwnd, "midiInReset", MIM_LONGDATA, (DWORD_PTR)&mhdr);
+
+    ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_DONE), "dwFlags=%x\n", mhdr.dwFlags);
+    rc = midiInUnprepareHeader(hm, &mhdr, sizeof(mhdr));
+    ok(!rc, "midiInUnprepare rc=%s\n", mmsys_error(rc));
+
+    ok(mhdr.dwBytesRecorded == 0, "Did some MIDI HW send %u bytes?\n", mhdr.dwBytesRecorded);
+
+    rc = midiInClose(hm);
+    ok(!rc, "midiInClose rc=%s\n", mmsys_error(rc));
+
+    ok(mhdr.dwUser==0x56FA552C, "MIDIHDR.dwUser changed to %lx\n", mhdr.dwUser);
+    HeapFree(GetProcessHeap(), 0, mhdr.lpData);
+    test_notification(hwnd, "midiInClose", MIM_CLOSE, 0);
+    test_notification(hwnd, "midiIn over", 0, WHATEVER);
+}
+
+static void test_midi_infns(HWND hwnd)
+{
+    HMIDIIN hm;
+    MMRESULT rc;
+    UINT udev, ndevs = midiInGetNumDevs();
+
+    rc = midiInOpen(&hm, ndevs, 0, 0, CALLBACK_NULL);
+    ok(rc==MMSYSERR_BADDEVICEID, "midiInOpen udev>max rc=%s\n", mmsys_error(rc));
+    if (!rc) {
+        rc = midiInClose(hm);
+        ok(!rc, "midiInClose rc=%s\n", mmsys_error(rc));
+    }
+    if (!ndevs) {
+        trace("Found no MIDI IN device\n"); /* no skip for this common situation */
+        rc = midiInOpen(&hm, MIDIMAPPER, 0, 0, CALLBACK_NULL);
+        ok(rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*nt,w2k*/), "midiInOpen MAPPER with no MIDI rc=%s\n", mmsys_error(rc));
+        if (!rc) {
+            rc = midiInClose(hm);
+            ok(!rc, "midiInClose rc=%s\n", mmsys_error(rc));
+        }
+        return;
+    }
+    trace("Found %d MIDI IN devices\n", ndevs);
+    for (udev=0; udev < ndevs; udev++) {
+        trace("** Testing device %d\n", udev);
+        test_midiIn_device(udev, hwnd);
+        Sleep(50);
+    }
+    trace("** Testing MIDI mapper\n");
+    test_midiIn_device(MIDIMAPPER, hwnd);
+}
+
+
+static void test_midi_mci(HWND hwnd)
+{
+    MCIERROR err;
+    char buf[1024];
+    memset(buf, 0, sizeof(buf));
+
+    err = mciSendStringA("sysinfo sequencer quantity", buf, sizeof(buf), hwnd);
+    ok(!err, "mci sysinfo sequencer quantity returned %d\n", err);
+    if (!err) trace("Found %s MCI sequencer devices\n", buf);
+
+    if (!strcmp(buf, "0")) return;
+
+    err = mciSendStringA("capability sequencer can record", buf, sizeof(buf), hwnd);
+    ok(!err, "mci sysinfo sequencer quantity returned %d\n", err);
+    if(!err) ok(!strcmp(buf, "false"), "capability can record is %s\n", buf);
+}
+
+
+static void test_midiOut_device(UINT udev, HWND hwnd)
+{
+    HMIDIOUT hm;
+    MMRESULT rc;
+    MIDIOUTCAPSA capsA;
+    DWORD ovolume;
+    UINT  udevid;
+    MIDIHDR mhdr;
+
+    rc = midiOutGetDevCapsA(udev, &capsA, sizeof(capsA));
+    ok(!rc, "midiOutGetDevCaps(dev=%d) rc=%s\n", udev, mmsys_error(rc));
+    if (!rc) {
+        trace("* %s: manufacturer=%d, product=%d, tech=%d, support=%X: %d voices, %d notes\n",
+              capsA.szPname, capsA.wMid, capsA.wPid, capsA.wTechnology, capsA.dwSupport, capsA.wVoices, capsA.wNotes);
+        ok(!((MIDIMAPPER==udev) ^ (MOD_MAPPER==capsA.wTechnology)), "technology %d on device %d\n", capsA.wTechnology, udev);
+        if (MOD_MIDIPORT == capsA.wTechnology) {
+            ok(capsA.wVoices == 0 && capsA.wNotes == 0, "external device with notes or voices\n");
+            ok(capsA.wChannelMask == 0xFFFF, "external device channel mask %x\n", capsA.wChannelMask);
+            ok(!(capsA.dwSupport & (MIDICAPS_VOLUME|MIDICAPS_LRVOLUME|MIDICAPS_CACHE)), "external device support=%X\n", capsA.dwSupport);
+        }
+    }
+
+    if (hwnd)
+        rc = midiOutOpen(&hm, udev, (DWORD_PTR)hwnd, (DWORD_PTR)MYCBINST, CALLBACK_WINDOW);
+    else
+        rc = midiOutOpen(&hm, udev, (DWORD_PTR)callback_func, (DWORD_PTR)MYCBINST, CALLBACK_FUNCTION);
+    if (rc == MMSYSERR_NOTSUPPORTED || rc == MMSYSERR_NODRIVER)
+    {
+        skip( "MIDI out not supported\n" );
+        return;
+    }
+    ok(!rc, "midiOutOpen(dev=%d) rc=%s\n", udev, mmsys_error(rc));
+    if (rc) return;
+
+    test_notification(hwnd, "midiOutOpen", MOM_OPEN, 0);
+
+    rc = midiOutGetVolume(hm, &ovolume);
+    ok((capsA.dwSupport & MIDICAPS_VOLUME) ? rc==MMSYSERR_NOERROR : rc==MMSYSERR_NOTSUPPORTED, "midiOutGetVolume rc=%s\n", mmsys_error(rc));
+    /* The native mapper responds with FFFFFFFF initially,
+     * real devices with the volume GUI SW-synth settings. */
+    if (!rc) trace("Current volume %x on device %d\n", ovolume, udev);
+
+    /* The W95 ESFM Synthesis device reports NOTENABLED although
+     * GetVolume by handle works and music plays. */
+    rc = midiOutGetVolume(UlongToHandle(udev), &ovolume);
+    ok((capsA.dwSupport & MIDICAPS_VOLUME) ? rc==MMSYSERR_NOERROR || broken(rc==MMSYSERR_NOTENABLED) : rc==MMSYSERR_NOTSUPPORTED, "midiOutGetVolume(dev=%d) rc=%s\n", udev, mmsys_error(rc));
+
+    rc = midiOutGetVolume(hm, NULL);
+    ok(rc==MMSYSERR_INVALPARAM, "midiOutGetVolume NULL rc=%s\n", mmsys_error(rc));
+
+    /* Tests with midiOutSetvolume show that the midi mapper forwards
+     * the value to the real device, but Get initially always reports
+     * FFFFFFFF.  Therefore, a Get+SetVolume pair with the mapper is
+     * not adequate to restore the value prior to tests.
+     */
+    if (winetest_interactive && (capsA.dwSupport & MIDICAPS_VOLUME)) {
+        DWORD volume2 = (ovolume < 0x80000000) ? 0xC000C000 : 0x40004000;
+        rc = midiOutSetVolume(hm, volume2);
+        ok(!rc, "midiOutSetVolume rc=%s\n", mmsys_error(rc));
+        if (!rc) {
+            DWORD volume3;
+            rc = midiOutGetVolume(hm, &volume3);
+            ok(!rc, "midiOutGetVolume new rc=%s\n", mmsys_error(rc));
+            if (!rc) trace("New volume %x on device %d\n", volume3, udev);
+            todo_wine ok(volume2==volume3, "volume Set %x = Get %x\n", volume2, volume3);
+
+            rc = midiOutSetVolume(hm, ovolume);
+            ok(!rc, "midiOutSetVolume restore rc=%s\n", mmsys_error(rc));
+        }
+    }
+    rc = midiOutGetDevCapsA((UINT_PTR)hm, &capsA, sizeof(capsA));
+    ok(!rc, "midiOutGetDevCaps(dev=%d) by handle rc=%s\n", udev, mmsys_error(rc));
+    rc = midiInGetDevCapsA((UINT_PTR)hm, (LPMIDIINCAPSA)&capsA, sizeof(DWORD));
+    ok(rc==MMSYSERR_BADDEVICEID, "midiInGetDevCaps(dev=%d) by out handle rc=%s\n", udev, mmsys_error(rc));
+
+    {   DWORD e = 0x006F4893; /* velocity, note (#69 would be 440Hz) channel */
+        trace("ShortMsg type %x\n", LOBYTE(LOWORD(e)));
+        rc = midiOutShortMsg(hm, e);
+        ok(!rc, "midiOutShortMsg rc=%s\n", mmsys_error(rc));
+        if (!rc) Sleep(400); /* Hear note */
+    }
+
+    memset(&mhdr, 0, sizeof(mhdr));
+    mhdr.dwFlags = MHDR_DONE;
+    mhdr.dwUser   = 0x56FA552C;
+    mhdr.dwOffset = 0xDEADBEEF;
+    mhdr.dwBufferLength = 70000; /* > 64KB! */
+    mhdr.lpData = HeapAlloc(GetProcessHeap(), 0 , mhdr.dwBufferLength);
+    ok(mhdr.lpData!=NULL, "No %d bytes of memory!\n", mhdr.dwBufferLength);
+    if (mhdr.lpData) {
+        rc = midiOutLongMsg(hm, &mhdr, sizeof(mhdr));
+        ok(rc==MIDIERR_UNPREPARED, "midiOutLongMsg unprepared rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == MHDR_DONE, "dwFlags=%x\n", mhdr.dwFlags);
+        test_notification(hwnd, "midiOutLong unprepared", 0, WHATEVER);
+
+        rc = midiOutPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset)-1);
+        ok(rc==MMSYSERR_INVALPARAM, "midiOutPrepare tiny rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == MHDR_DONE, "dwFlags=%x\n", mhdr.dwFlags);
+
+        /* Since at least w2k, midiOutPrepare clears the DONE and INQUEUE flags.  w95 didn't. */
+        /* mhdr.dwFlags |= MHDR_INQUEUE; would cause w95 to return STILLPLAYING from Unprepare */
+        rc = midiOutPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset));
+        ok(!rc, "midiOutPrepare old size rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_DONE)/*w9x*/ ||
+           mhdr.dwFlags == MHDR_PREPARED, "dwFlags=%x\n", mhdr.dwFlags);
+        trace("MIDIHDR flags=%x when unsent\n", mhdr.dwFlags);
+
+        /* No flag is cleared when already prepared. */
+        mhdr.dwFlags |= MHDR_DONE|MHDR_INQUEUE;
+        rc = midiOutPrepareHeader(hm, &mhdr, sizeof(mhdr));
+        ok(!rc, "midiOutPrepare rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_DONE|MHDR_INQUEUE), "dwFlags=%x\n", mhdr.dwFlags);
+
+        mhdr.dwFlags |= MHDR_INQUEUE;
+        rc = midiOutUnprepareHeader(hm, &mhdr, sizeof(mhdr));
+        ok(rc==MIDIERR_STILLPLAYING, "midiOutUnprepare rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_DONE|MHDR_INQUEUE), "dwFlags=%x\n", mhdr.dwFlags);
+
+        mhdr.dwFlags &= ~MHDR_INQUEUE;
+        rc = midiOutUnprepareHeader(hm, &mhdr, sizeof(mhdr));
+        ok(!rc, "midiOutUnprepare rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == MHDR_DONE, "dwFlags=%x\n", mhdr.dwFlags);
+
+        mhdr.dwFlags |= MHDR_INQUEUE;
+        rc = midiOutUnprepareHeader(hm, &mhdr, sizeof(mhdr));
+        ok(!rc, "midiOutUnprepare rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags == (MHDR_INQUEUE|MHDR_DONE), "dwFlags=%x\n", mhdr.dwFlags);
+
+        HeapFree(GetProcessHeap(), 0, mhdr.lpData);
+    }
+    ok(mhdr.dwUser==0x56FA552C, "MIDIHDR.dwUser changed to %lx\n", mhdr.dwUser);
+    ok(mhdr.dwOffset==0xDEADBEEF, "MIDIHDR.dwOffset changed to %x\n", mhdr.dwOffset);
+
+    rc = midiOutGetID(hm, &udevid);
+    ok(!rc, "midiOutGetID rc=%s\n", mmsys_error(rc));
+    if(!rc) ok(udevid==udev, "midiOutGetID gives %d, expect %d\n", udevid, udev);
+
+    rc = midiOutReset(hm); /* Quiet everything */
+    ok(!rc, "midiOutReset rc=%s\n", mmsys_error(rc));
+
+    rc = midiOutClose(hm);
+    ok(!rc, "midiOutClose rc=%s\n", mmsys_error(rc));
+    test_notification(hwnd, "midiOutClose", MOM_CLOSE, 0);
+
+    rc = midiOutOpen(&hm, udev, 0, (DWORD_PTR)MYCBINST, CALLBACK_WINDOW);
+    /* w95 broken(rc==MMSYSERR_INVALPARAM) see WINMM_CheckCallback */
+    ok(!rc, "midiOutOpen(dev=%d) 0 CALLBACK_WINDOW rc=%s\n", udev, mmsys_error(rc));
+    /* PostMessage(hwnd=0) redirects to PostThreadMessage(GetCurrentThreadId())
+     * which PeekMessage((HWND)-1) queries. */
+    test_notification((HWND)-1, "midiOutOpen WINDOW->THREAD", 0, WHATEVER);
+    test_notification(hwnd, "midiOutOpen WINDOW", 0, WHATEVER);
+    if (!rc) {
+        rc = midiOutClose(hm);
+        ok(!rc, "midiOutClose rc=%s\n", mmsys_error(rc));
+        test_notification((HWND)-1, "midiOutClose WINDOW->THREAD", 0, WHATEVER);
+        test_notification(hwnd, "midiOutClose", 0, WHATEVER);
+    }
+    test_notification(hwnd, "midiOut over", 0, WHATEVER);
+}
+
+static void test_position(HMIDISTRM hm, UINT typein, UINT typeout)
+{
+    MMRESULT rc;
+    MMTIME mmtime;
+    mmtime.wType = typein;
+    rc = midiStreamPosition(hm, &mmtime, sizeof(MMTIME));
+    /* Ugly, but a single ok() herein enables using the todo_wine prefix */
+    ok(!rc && (mmtime.wType == typeout), "midiStreamPosition type %x converted to %x rc=%s\n", typein, mmtime.wType, mmsys_error(rc));
+    if (!rc) switch(mmtime.wType) {
+    case TIME_MS:
+        trace("Stream position %ums\n", mmtime.u.ms);
+        break;
+    case TIME_TICKS:
+        trace("Stream position %u ticks\n", mmtime.u.ticks);
+        break;
+    case TIME_MIDI:
+        trace("Stream position song pointer %u\n", mmtime.u.midi.songptrpos);
+        break;
+    }
+}
+
+typedef struct midishortevent_tag { /* ideal size for MEVT_F_SHORT event type */
+    DWORD dwDeltaTime;
+    DWORD dwStreamID;
+    DWORD dwEvent;
+} MIDISHORTEVENT;
+
+/* Native crashes on a second run with the const qualifier set on this data! */
+static BYTE strmEvents[] = { /* A set of variable-sized MIDIEVENT structs */
+    0, 0, 0, 0,  0, 0, 0, 0, /* dwDeltaTime and dwStreamID */
+    0, 0, 0, MEVT_NOP | 0x40, /* with MEVT_F_CALLBACK */
+    0, 0, 0, 0,  0, 0, 0, 0,
+    0xE0, 0x93, 0x04, MEVT_TEMPO, /* 0493E0 == 300000 */
+    0, 0, 0, 0,  0, 0, 0, 0,
+    0x93, 0x48, 0x6F, MEVT_SHORTMSG,
+};
+
+static MIDISHORTEVENT strmNops[] = { /* Test callback + dwOffset */
+  { 0, 0, (MEVT_NOP <<24)| MEVT_F_CALLBACK },
+  { 0, 0, (MEVT_NOP <<24)| MEVT_F_CALLBACK },
+};
+
+static MMRESULT playStream(HMIDISTRM hm, LPMIDIHDR lpMidiHdr)
+{
+    MMRESULT rc = midiStreamOut(hm, lpMidiHdr, sizeof(MIDIHDR));
+    /* virtual machines may return MIDIERR_STILLPLAYING from the next request
+     * even after MHDR_DONE is set. It's still too early, so add MHDR_INQUEUE. */
+    if (!rc) while (!(lpMidiHdr->dwFlags & MHDR_DONE) || (lpMidiHdr->dwFlags & MHDR_INQUEUE)) { Sleep(100); }
+    return rc;
+}
+
+static void test_midiStream(UINT udev, HWND hwnd)
+{
+    HMIDISTRM hm;
+    MMRESULT rc, rc2;
+    MIDIHDR mhdr;
+    union {
+        MIDIPROPTEMPO tempo;
+        MIDIPROPTIMEDIV tdiv;
+    } midiprop;
+
+    if (hwnd)
+        rc = midiStreamOpen(&hm, &udev, 1, (DWORD_PTR)hwnd, (DWORD_PTR)MYCBINST, CALLBACK_WINDOW);
+    else
+        rc = midiStreamOpen(&hm, &udev, 1, (DWORD_PTR)callback_func, (DWORD_PTR)MYCBINST, CALLBACK_FUNCTION);
+    if (rc == MMSYSERR_NOTSUPPORTED || rc == MMSYSERR_NODRIVER)
+    {
+        skip( "MIDI stream not supported\n" );
+        return;
+    }
+    ok(!rc, "midiStreamOpen(dev=%d) rc=%s\n", udev, mmsys_error(rc));
+    if (rc) return;
+
+    test_notification(hwnd, "midiStreamOpen", MOM_OPEN, 0);
+
+    midiprop.tempo.cbStruct = sizeof(midiprop.tempo);
+    rc = midiStreamProperty(hm, (void*)&midiprop, MIDIPROP_GET|MIDIPROP_TEMPO);
+    ok(!rc, "midiStreamProperty TEMPO rc=%s\n", mmsys_error(rc));
+    ok(midiprop.tempo.dwTempo==500000, "default stream tempo %u microsec per quarter note\n", midiprop.tempo.dwTempo);
+
+    midiprop.tdiv.cbStruct = sizeof(midiprop.tdiv);
+    rc = midiStreamProperty(hm, (void*)&midiprop, MIDIPROP_GET|MIDIPROP_TIMEDIV);
+    ok(!rc, "midiStreamProperty TIMEDIV rc=%s\n", mmsys_error(rc));
+    todo_wine ok(24==LOWORD(midiprop.tdiv.dwTimeDiv), "default stream time division %u\n", midiprop.tdiv.dwTimeDiv);
+
+    memset(&mhdr, 0, sizeof(mhdr));
+    mhdr.dwUser   = 0x56FA552C;
+    mhdr.dwOffset = 1234567890;
+    mhdr.dwBufferLength = sizeof(strmEvents);
+    mhdr.dwBytesRecorded = mhdr.dwBufferLength;
+    mhdr.lpData = (LPSTR)&strmEvents[0];
+    if (mhdr.lpData) {
+        rc = midiOutLongMsg((HMIDIOUT)hm, &mhdr, sizeof(mhdr));
+        ok(rc==MIDIERR_UNPREPARED, "midiOutLongMsg unprepared rc=%s\n", mmsys_error(rc));
+        test_notification(hwnd, "midiOutLong unprepared", 0, WHATEVER);
+
+        rc = midiOutPrepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset)-1);
+        ok(rc==MMSYSERR_INVALPARAM, "midiOutPrepare tiny rc=%s\n", mmsys_error(rc));
+        rc = midiOutPrepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset));
+        ok(!rc, "midiOutPrepare old size rc=%s\n", mmsys_error(rc));
+        ok(mhdr.dwFlags & MHDR_PREPARED, "MHDR.dwFlags when prepared %x\n", mhdr.dwFlags);
+
+        /* The device is still in paused mode and should queue the message. */
+        rc = midiStreamOut(hm, &mhdr, offsetof(MIDIHDR,dwOffset));
+        ok(!rc, "midiStreamOut old size rc=%s\n", mmsys_error(rc));
+        rc2 = rc;
+        trace("MIDIHDR flags=%x when submitted\n", mhdr.dwFlags);
+        /* w9X/me does not set MHDR_ISSTRM when StreamOut exits,
+         * but it will be set on all systems after the job is finished. */
+
+        Sleep(90);
+        /* Wine <1.1.39 started playing immediately */
+        test_notification(hwnd, "midiStream still paused", 0, WHATEVER);
+
+    /* MSDN asks to use midiStreamRestart prior to midiStreamOut()
+     * because the starting state is 'pause', but some apps seem to
+     * work with the inverse order: queue everything, then play.
+     */
+
+        rc = midiStreamRestart(hm);
+        ok(!rc, "midiStreamRestart rc=%s\n", mmsys_error(rc));
+
+        if (!rc2) while(mhdr.dwFlags & MHDR_INQUEUE) {
+            trace("async MIDI still queued\n");
+            Sleep(100);
+        } /* Checking INQUEUE is not the recommended way to wait for the end of a job, but we're testing. */
+        /* MHDR_ISSTRM is not necessarily set when midiStreamOut returns
+         * rather than when the queue is eventually processed. */
+        ok(mhdr.dwFlags & MHDR_ISSTRM, "MHDR.dwFlags %x no ISSTRM when out of queue\n", mhdr.dwFlags);
+        if (!rc2) while(!(mhdr.dwFlags & MHDR_DONE)) {
+            /* Never to be seen except perhaps on multicore */
+            trace("async MIDI still not done\n");
+            Sleep(100);
+        }
+        ok(mhdr.dwFlags & MHDR_DONE, "MHDR.dwFlags %x not DONE when out of queue\n", mhdr.dwFlags);
+        test_notification(hwnd, "midiStream callback", MOM_POSITIONCB, (DWORD_PTR)&mhdr);
+        test_notification(hwnd, "midiStreamOut", MOM_DONE, (DWORD_PTR)&mhdr);
+
+        /* Native fills dwOffset regardless of the cbMidiHdr size argument to midiStreamOut */
+        ok(1234567890!=mhdr.dwOffset, "play left MIDIHDR.dwOffset at %u\n", mhdr.dwOffset);
+
+        rc = midiOutUnprepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset));
+        ok(!rc, "midiOutUnprepare rc=%s\n", mmsys_error(rc));
+        rc = midiOutUnprepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset));
+        ok(!rc, "midiOutUnprepare #2 rc=%s\n", mmsys_error(rc));
+
+        trace("MIDIHDR stream flags=%x when finished\n", mhdr.dwFlags);
+        ok(mhdr.dwFlags & MHDR_DONE, "MHDR.dwFlags when done %x\n", mhdr.dwFlags);
+
+        test_position(hm, TIME_MS,      TIME_MS);
+        test_position(hm, TIME_TICKS,   TIME_TICKS);
+        todo_wine test_position(hm, TIME_MIDI,    TIME_MIDI);
+        test_position(hm, TIME_SMPTE,   TIME_MS);
+        test_position(hm, TIME_SAMPLES, TIME_MS);
+        test_position(hm, TIME_BYTES,   TIME_MS);
+
+        Sleep(400); /* Hear note */
+
+        midiprop.tempo.cbStruct = sizeof(midiprop.tempo);
+        rc = midiStreamProperty(hm, (void*)&midiprop, MIDIPROP_GET|MIDIPROP_TEMPO);
+        ok(!rc, "midiStreamProperty TEMPO rc=%s\n", mmsys_error(rc));
+        ok(0x0493E0==midiprop.tempo.dwTempo, "stream set tempo %u\n", midiprop.tdiv.dwTimeDiv);
+
+        rc = midiStreamRestart(hm);
+        ok(!rc, "midiStreamRestart #2 rc=%s\n", mmsys_error(rc));
+
+        mhdr.dwFlags |= MHDR_ISSTRM;
+        /* Preset flags (e.g. MHDR_ISSTRM) do not disturb. */
+        rc = midiOutPrepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset));
+        ok(!rc, "midiOutPrepare used flags %x rc=%s\n", mhdr.dwFlags, mmsys_error(rc));
+        rc = midiOutUnprepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset));
+        ok(!rc, "midiOutUnprepare used flags %x rc=%s\n", mhdr.dwFlags, mmsys_error(rc));
+
+        rc = midiStreamRestart(hm);
+        ok(!rc, "midiStreamRestart #3 rc=%s\n", mmsys_error(rc));
+    }
+    ok(mhdr.dwUser==0x56FA552C, "MIDIHDR.dwUser changed to %lx\n", mhdr.dwUser);
+    ok(0==((MIDISHORTEVENT*)&strmEvents)[0].dwStreamID, "dwStreamID set to %x\n", ((LPMIDIEVENT)&strmEvents[0])->dwStreamID);
+
+    /* dwBytesRecorded controls how much is played, not dwBufferLength
+     * allowing to immediately forward packets from midiIn to midiOut */
+    mhdr.dwOffset = 1234123123;
+    mhdr.dwBufferLength = sizeof(strmNops);
+    trace("buffer: %u\n", mhdr.dwBufferLength);
+    mhdr.dwBytesRecorded = 0;
+    mhdr.lpData = (LPSTR)&strmNops[0];
+    strmNops[0].dwEvent |= MEVT_F_CALLBACK;
+    strmNops[1].dwEvent |= MEVT_F_CALLBACK;
+
+    rc = midiOutPrepareHeader((HMIDIOUT)hm, &mhdr, sizeof(mhdr));
+    ok(!rc, "midiOutPrepare rc=%s\n", mmsys_error(rc));
+
+    rc = playStream(hm, &mhdr);
+    ok(!rc, "midiStreamOut 0 bytes recorded rc=%s\n", mmsys_error(rc));
+
+    test_notification(hwnd, "midiStreamOut", MOM_DONE, (DWORD_PTR)&mhdr);
+    test_notification(hwnd, "0 bytes recorded", 0, WHATEVER);
+
+    /* FIXME: check dwOffset within callback
+     * instead of the unspecified value afterwards */
+    ok(1234123123==mhdr.dwOffset || broken(0==mhdr.dwOffset), "play 0 set MIDIHDR.dwOffset to %u\n", mhdr.dwOffset);
+    /* w2k and later only set dwOffset when processing MEVT_T_CALLBACK,
+     * while w9X/me/nt always sets it.  Have Wine behave like w2k because the
+     * dwOffset slot does not exist in the small size MIDIHDR. */
+
+    mhdr.dwOffset = 1234123123;
+    mhdr.dwBytesRecorded = 1*sizeof(MIDISHORTEVENT);
+
+    rc = playStream(hm, &mhdr);
+    ok(!rc, "midiStreamOut 1 event out of 2 rc=%s\n", mmsys_error(rc));
+
+    test_notification(hwnd, "1 of 2 events", MOM_POSITIONCB, (DWORD_PTR)&mhdr);
+    test_notification(hwnd, "1 of 2 events", MOM_DONE, (DWORD_PTR)&mhdr);
+    test_notification(hwnd, "1 of 2 events", 0, WHATEVER);
+    ok(0==mhdr.dwOffset, "MIDIHDR.dwOffset 1/2 changed to %u\n", mhdr.dwOffset);
+
+    mhdr.dwOffset = 1234123123;
+    mhdr.dwBytesRecorded = 2*sizeof(MIDISHORTEVENT);
+
+    rc = playStream(hm, &mhdr);
+    ok(!rc, "midiStreamOut 1 event out of 2 rc=%s\n", mmsys_error(rc));
+
+    test_notification(hwnd, "2 of 2 events", MOM_POSITIONCB, (DWORD_PTR)&mhdr);
+    test_notification(hwnd, "2 of 2 events", MOM_POSITIONCB, (DWORD_PTR)&mhdr);
+    test_notification(hwnd, "2 of 2 events", MOM_DONE, (DWORD_PTR)&mhdr);
+    test_notification(hwnd, "2 of 2 events", 0, WHATEVER);
+    ok(sizeof(MIDISHORTEVENT)==mhdr.dwOffset, "MIDIHDR.dwOffset 2/2 changed to %u\n", mhdr.dwOffset);
+    ok(mhdr.dwBytesRecorded == 2*sizeof(MIDISHORTEVENT), "dwBytesRecorded changed to %u\n", mhdr.dwBytesRecorded);
+
+    strmNops[0].dwEvent &= ~MEVT_F_CALLBACK;
+    strmNops[1].dwEvent &= ~MEVT_F_CALLBACK;
+    mhdr.dwOffset = 1234123123;
+    rc = playStream(hm, &mhdr);
+    ok(!rc, "midiStreamOut 1 event out of 2 rc=%s\n", mmsys_error(rc));
+
+    test_notification(hwnd, "0 CB in 2 events", MOM_DONE, (DWORD_PTR)&mhdr);
+    test_notification(hwnd, "0 CB in 2 events", 0, WHATEVER);
+    /* w9X/me/nt set dwOffset to the position played last */
+    ok(1234123123==mhdr.dwOffset || broken(sizeof(MIDISHORTEVENT)==mhdr.dwOffset), "MIDIHDR.dwOffset nocb changed to %u\n", mhdr.dwOffset);
+
+    mhdr.dwBytesRecorded = mhdr.dwBufferLength-1;
+    rc = playStream(hm, &mhdr);
+    ok(rc==MMSYSERR_INVALPARAM,"midiStreamOut dwBytesRecorded modulo MIDIEVENT rc=%s\n", mmsys_error(rc));
+    if (!rc) {
+         test_notification(hwnd, "2 of 2 events", MOM_DONE, (DWORD_PTR)&mhdr);
+    }
+
+    mhdr.dwBytesRecorded = mhdr.dwBufferLength+1;
+    rc = playStream(hm, &mhdr);
+    ok(rc==MMSYSERR_INVALPARAM,"midiStreamOut dwBufferLength<dwBytesRecorded rc=%s\n", mmsys_error(rc));
+    test_notification(hwnd, "past MIDIHDR tests", 0, WHATEVER);
+
+    rc = midiStreamStop(hm);
+    ok(!rc, "midiStreamStop rc=%s\n", mmsys_error(rc));
+    ok(mhdr.dwUser==0x56FA552C, "MIDIHDR.dwUser changed to %lx\n", mhdr.dwUser);
+
+    rc = midiOutUnprepareHeader((HMIDIOUT)hm, &mhdr, sizeof(mhdr));
+    ok(!rc, "midiOutUnprepare rc=%s\n", mmsys_error(rc));
+    ok(0==strmNops[0].dwStreamID, "dwStreamID[0] set to %x\n", strmNops[0].dwStreamID);
+    ok(0==strmNops[1].dwStreamID, "dwStreamID[1] set to %x\n", strmNops[1].dwStreamID);
+
+    mhdr.dwBufferLength = 70000; /* > 64KB! */
+    mhdr.lpData = HeapAlloc(GetProcessHeap(), 0 , mhdr.dwBufferLength);
+    ok(mhdr.lpData!=NULL, "No %d bytes of memory!\n", mhdr.dwBufferLength);
+    if (mhdr.lpData) {
+        mhdr.dwFlags = 0;
+        /* PrepareHeader detects the too large buffer is for a stream. */
+        rc = midiOutPrepareHeader((HMIDIOUT)hm, &mhdr, sizeof(mhdr));
+        todo_wine ok(rc==MMSYSERR_INVALPARAM, "midiOutPrepare stream too large rc=%s\n", mmsys_error(rc));
+
+        rc = midiOutUnprepareHeader((HMIDIOUT)hm, &mhdr, sizeof(mhdr));
+        ok(!rc, "midiOutUnprepare rc=%s\n", mmsys_error(rc));
+
+        HeapFree(GetProcessHeap(), 0, mhdr.lpData);
+    }
+
+    rc = midiStreamClose(hm);
+    ok(!rc, "midiStreamClose rc=%s\n", mmsys_error(rc));
+    test_notification(hwnd, "midiStreamClose", MOM_CLOSE, 0);
+    test_notification(hwnd, "midiStream over", 0, WHATEVER);
+
+    rc = midiStreamOpen(&hm, &udev, 1, 0, (DWORD_PTR)MYCBINST, CALLBACK_FUNCTION);
+    ok(!rc /*w2k*/|| rc==MMSYSERR_INVALPARAM/*w98*/, "midiStreamOpen NULL function rc=%s\n", mmsys_error(rc));
+    if (!rc) {
+        trace("Device %d accepts NULL CALLBACK_FUNCTION\n", udev);
+        rc = midiStreamClose(hm);
+        ok(!rc, "midiStreamClose rc=%s\n", mmsys_error(rc));
+    }
+
+    rc = midiStreamOpen(&hm, &udev, 1, (DWORD_PTR)0xDEADBEEF, (DWORD_PTR)MYCBINST, CALLBACK_WINDOW);
+    ok(rc==MMSYSERR_INVALPARAM, "midiStreamOpen bad window rc=%s\n", mmsys_error(rc));
+    if (!rc) {
+        rc = midiStreamClose(hm);
+        ok(!rc, "midiStreamClose rc=%s\n", mmsys_error(rc));
+    }
+}
+
+static BOOL scan_subkeys(HKEY parent, const LPCSTR *sub_keys)
+{
+    char name[64];
+    DWORD index = 0;
+    DWORD name_len = sizeof(name);
+    BOOL found_vmware = FALSE;
+
+    if (sub_keys[0] == NULL)
+    {
+       /* We're at the deepest level, check "Identifier" value now */
+       char *test;
+       if (RegQueryValueExA(parent, "Identifier", NULL, NULL, (LPBYTE) name, &name_len) != ERROR_SUCCESS)
+           return FALSE;
+       for (test = name; test < name + lstrlenA(name) - 6 && ! found_vmware; test++)
+       {
+           char c = test[6];
+           test[6] = '\0';
+           found_vmware = (lstrcmpiA(test, "VMware") == 0);
+           test[6] = c;
+       }
+       return found_vmware;
+    }
+
+    while (RegEnumKeyExA(parent, index, name, &name_len, NULL, NULL, NULL, NULL) == ERROR_SUCCESS &&
+           ! found_vmware) {
+        char c = name[lstrlenA(sub_keys[0])];
+        name[lstrlenA(sub_keys[0])] = '\0';
+        if (lstrcmpiA(name, sub_keys[0]) == 0) {
+            HKEY sub_key;
+            name[lstrlenA(sub_keys[0])] = c;
+            if (RegOpenKeyExA(parent, name, 0, KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE, &sub_key) == ERROR_SUCCESS) {
+                found_vmware = scan_subkeys(sub_key, sub_keys + 1);
+                RegCloseKey(sub_key);
+            }
+        }
+
+        name_len = sizeof(name);
+        index++;
+    }
+
+    return found_vmware;
+}
+
+/*
+ * Usual method to detect whether running inside a VMware virtual machine involves direct port I/O requiring
+ * some assembly and an exception handler. Can't do that in Wine tests. Alternative method of querying WMI
+ * is not available on NT4. So instead we look at the device map and check the Identifier value in the
+ * registry keys HKLM\HARDWARE\DEVICEMAP\SCSI\Scsi Port x\Scsi Bus x\Target Id x\Logical Unit Id x (where
+ * x is some number). If the Identifier value contains the string "VMware" we assume running in a VMware VM.
+ */
+static BOOL on_vmware(void)
+{
+    static const LPCSTR sub_keys[] = { "Scsi Port ", "Scsi Bus ", "Target Id ", "Logical Unit Id ", NULL };
+    HKEY scsi;
+    BOOL found_vmware = FALSE;
+
+    if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\Scsi", 0, KEY_ENUMERATE_SUB_KEYS, &scsi) != ERROR_SUCCESS)
+        return FALSE;
+
+    found_vmware = scan_subkeys(scsi, sub_keys);
+
+    RegCloseKey(scsi);
+
+    return found_vmware;
+}
+
+static void test_midi_outfns(HWND hwnd)
+{
+    HMIDIOUT hm;
+    MMRESULT rc;
+    UINT udev, ndevs = midiOutGetNumDevs();
+
+    rc = midiOutOpen(&hm, ndevs, 0, 0, CALLBACK_NULL);
+    ok(rc==MMSYSERR_BADDEVICEID, "midiOutOpen udev>max rc=%s\n", mmsys_error(rc));
+    if (!rc) {
+        rc = midiOutClose(hm);
+        ok(!rc, "midiOutClose rc=%s\n", mmsys_error(rc));
+    }
+    if (!ndevs) {
+        MIDIOUTCAPSA capsA;
+        skip("Found no MIDI out device\n");
+
+        rc = midiOutGetDevCapsA(MIDIMAPPER, &capsA, sizeof(capsA));
+        /* GetDevCaps and Open must return compatible results */
+        ok(rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*nt,w2k*/), "midiOutGetDevCaps MAPPER with no MIDI rc=%s\n", mmsys_error(rc));
+
+        rc = midiOutOpen(&hm, MIDIMAPPER, 0, 0, CALLBACK_NULL);
+        if (rc==MIDIERR_INVALIDSETUP) todo_wine /* Wine without snd-seq */
+        ok(rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*w2k*/), "midiOutOpen MAPPER with no MIDI rc=%s\n", mmsys_error(rc));
+        else
+        ok(rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*w2k sound disabled*/),
+           "midiOutOpen MAPPER with no MIDI rc=%s\n", mmsys_error(rc));
+        if (!rc) {
+            rc = midiOutClose(hm);
+            ok(!rc, "midiOutClose rc=%s\n", mmsys_error(rc));
+        }
+        return;
+    }
+    trace("Found %d MIDI OUT devices\n", ndevs);
+
+    test_midi_mci(hwnd);
+
+    for (udev=0; udev < ndevs; udev++) {
+        MIDIOUTCAPSA capsA;
+        rc = midiOutGetDevCapsA(udev, &capsA, sizeof(capsA));
+        if (rc || strcmp(capsA.szPname, "Creative Sound Blaster MPU-401") != 0 || ! on_vmware()) {
+            trace("** Testing device %d\n", udev);
+            test_midiOut_device(udev, hwnd);
+            Sleep(800); /* Let the synth rest */
+            test_midiStream(udev, hwnd);
+            Sleep(800);
+        }
+        else
+            win_skip("Skipping this device on VMware, driver problem\n");
+    }
+    trace("** Testing MIDI mapper\n");
+    test_midiOut_device(MIDIMAPPER, hwnd);
+    Sleep(800);
+    test_midiStream(MIDIMAPPER, hwnd);
+}
+
+START_TEST(midi)
+{
+    HWND hwnd = 0;
+    if (1) /* select 1 for CALLBACK_WINDOW or 0 for CALLBACK_FUNCTION */
+    hwnd = CreateWindowExA(0, "static", "winmm midi test", WS_POPUP, 0,0,100,100,
+                           0, 0, 0, NULL);
+    test_midi_infns(hwnd);
+    test_midi_outfns(hwnd);
+    if (hwnd) DestroyWindow(hwnd);
+}
index 59627a7..82eb338 100644 (file)
@@ -184,7 +184,7 @@ static void test_mixerClose(HMIXER mix)
        mmsys_error(rc));
 }
 
-static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control)
+static void mixer_test_controlA(HMIXEROBJ mix, MIXERCONTROLA *control)
 {
     MMRESULT rc;
 
@@ -197,11 +197,18 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control)
         details.dwControlID = control->dwControlID;
         details.cChannels = 1;
         U(details).cMultipleItems = 0;
-        details.paDetails = &value;
         details.cbDetails = sizeof(value);
 
+        /* test NULL paDetails */
+        details.paDetails = NULL;
+        rc = mixerGetControlDetailsA(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE);
+        ok(rc==MMSYSERR_INVALPARAM,
+           "mixerGetDevCapsA: MMSYSERR_INVALPARAM expected, got %s\n",
+           mmsys_error(rc));
+
         /* read the current control value */
-        rc=mixerGetControlDetails((HMIXEROBJ)mix,&details,MIXER_GETCONTROLDETAILSF_VALUE);
+        details.paDetails = &value;
+        rc = mixerGetControlDetailsA(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE);
         ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
            "MMSYSERR_NOERROR expected, got %s\n",
            mmsys_error(rc));
@@ -224,7 +231,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control)
             new_details.cbDetails = sizeof(new_value);
 
             /* change the control value by one step */
-            rc=mixerSetControlDetails((HMIXEROBJ)mix,&new_details,MIXER_SETCONTROLDETAILSF_VALUE);
+            rc = mixerSetControlDetails(mix, &new_details, MIXER_SETCONTROLDETAILSF_VALUE);
             ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                "MMSYSERR_NOERROR expected, got %s\n",
                mmsys_error(rc));
@@ -240,7 +247,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control)
                 ret_details.cbDetails = sizeof(ret_value);
 
                 /* read back the new control value */
-                rc=mixerGetControlDetails((HMIXEROBJ)mix,&ret_details,MIXER_GETCONTROLDETAILSF_VALUE);
+                rc = mixerGetControlDetailsA(mix, &ret_details, MIXER_GETCONTROLDETAILSF_VALUE);
                 ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
                    "MMSYSERR_NOERROR expected, got %s\n",
                    mmsys_error(rc));
@@ -259,7 +266,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control)
                         details.cbDetails = sizeof(value);
 
                         /* restore original value */
-                        rc=mixerSetControlDetails((HMIXEROBJ)mix,&details,MIXER_SETCONTROLDETAILSF_VALUE);
+                        rc = mixerSetControlDetails(mix, &details, MIXER_SETCONTROLDETAILSF_VALUE);
                         ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                            "MMSYSERR_NOERROR expected, got %s\n",
                            mmsys_error(rc));
@@ -280,7 +287,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control)
         details.paDetails = &value;
         details.cbDetails = sizeof(value);
 
-        rc=mixerGetControlDetails((HMIXEROBJ)mix,&details,MIXER_GETCONTROLDETAILSF_VALUE);
+        rc = mixerGetControlDetailsA(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE);
         ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
            "MMSYSERR_NOERROR expected, got %s\n",
            mmsys_error(rc));
@@ -303,7 +310,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control)
             new_details.cbDetails = sizeof(new_value);
 
             /* change the control value by one step */
-            rc=mixerSetControlDetails((HMIXEROBJ)mix,&new_details,MIXER_SETCONTROLDETAILSF_VALUE);
+            rc = mixerSetControlDetails(mix, &new_details, MIXER_SETCONTROLDETAILSF_VALUE);
             ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                "MMSYSERR_NOERROR expected, got %s\n",
                mmsys_error(rc));
@@ -319,7 +326,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control)
                 ret_details.cbDetails = sizeof(ret_value);
 
                 /* read back the new control value */
-                rc=mixerGetControlDetails((HMIXEROBJ)mix,&ret_details,MIXER_GETCONTROLDETAILSF_VALUE);
+                rc = mixerGetControlDetailsA(mix, &ret_details, MIXER_GETCONTROLDETAILSF_VALUE);
                 ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
                    "MMSYSERR_NOERROR expected, got %s\n",
                    mmsys_error(rc));
@@ -338,7 +345,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control)
                         details.cbDetails = sizeof(value);
 
                         /* restore original value */
-                        rc=mixerSetControlDetails((HMIXEROBJ)mix,&details,MIXER_SETCONTROLDETAILSF_VALUE);
+                        rc = mixerSetControlDetails(mix, &details, MIXER_SETCONTROLDETAILSF_VALUE);
                         ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                            "MMSYSERR_NOERROR expected, got %s\n",
                            mmsys_error(rc));
@@ -354,7 +361,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control)
 static void mixer_test_deviceA(int device)
 {
     MIXERCAPSA capsA;
-    HMIXER mix;
+    HMIXEROBJ mix;
     MMRESULT rc;
     DWORD d,s,ns,nc;
 
@@ -384,16 +391,23 @@ static void mixer_test_deviceA(int device)
               capsA.vDriverVersion & 0xff,capsA.wMid,capsA.wPid);
     }
 
-    rc=mixerOpen(&mix, device, 0, 0, 0);
+    rc = mixerOpen((HMIXER*)&mix, device, 0, 0, 0);
     ok(rc==MMSYSERR_NOERROR,
        "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",mmsys_error(rc));
     if (rc==MMSYSERR_NOERROR) {
+        MIXERCAPSA capsA2;
+
+        rc=mixerGetDevCapsA((UINT_PTR)mix,&capsA2,sizeof(capsA2));
+        ok(rc==MMSYSERR_NOERROR,
+           "mixerGetDevCapsA: MMSYSERR_NOERROR expected, got %s\n",
+           mmsys_error(rc));
+        ok(!strcmp(capsA2.szPname, capsA.szPname), "Got wrong device caps\n");
+
         for (d=0;d<capsA.cDestinations;d++) {
             MIXERLINEA mixerlineA;
             mixerlineA.cbStruct = 0;
             mixerlineA.dwDestination=d;
-            rc=mixerGetLineInfoA((HMIXEROBJ)mix,&mixerlineA,
-                                 MIXER_GETLINEINFOF_DESTINATION);
+            rc = mixerGetLineInfoA(mix, &mixerlineA, MIXER_GETLINEINFOF_DESTINATION);
             ok(rc==MMSYSERR_INVALPARAM,
                "mixerGetLineInfoA(MIXER_GETLINEINFOF_DESTINATION): "
                "MMSYSERR_INVALPARAM expected, got %s\n",
@@ -401,8 +415,7 @@ static void mixer_test_deviceA(int device)
 
             mixerlineA.cbStruct = sizeof(mixerlineA);
             mixerlineA.dwDestination=capsA.cDestinations;
-            rc=mixerGetLineInfoA((HMIXEROBJ)mix,&mixerlineA,
-                                 MIXER_GETLINEINFOF_DESTINATION);
+            rc = mixerGetLineInfoA(mix, &mixerlineA, MIXER_GETLINEINFOF_DESTINATION);
             ok(rc==MMSYSERR_INVALPARAM||rc==MIXERR_INVALLINE,
                "mixerGetLineInfoA(MIXER_GETLINEINFOF_DESTINATION): "
                "MMSYSERR_INVALPARAM or MIXERR_INVALLINE expected, got %s\n",
@@ -410,8 +423,7 @@ static void mixer_test_deviceA(int device)
 
             mixerlineA.cbStruct = sizeof(mixerlineA);
             mixerlineA.dwDestination=d;
-            rc=mixerGetLineInfoA((HMIXEROBJ)mix,0,
-                                 MIXER_GETLINEINFOF_DESTINATION);
+            rc = mixerGetLineInfoA(mix, 0, MIXER_GETLINEINFOF_DESTINATION);
             ok(rc==MMSYSERR_INVALPARAM,
                "mixerGetLineInfoA(MIXER_GETLINEINFOF_DESTINATION): "
                "MMSYSERR_INVALPARAM expected, got %s\n",
@@ -419,15 +431,14 @@ static void mixer_test_deviceA(int device)
 
             mixerlineA.cbStruct = sizeof(mixerlineA);
             mixerlineA.dwDestination=d;
-            rc=mixerGetLineInfoA((HMIXEROBJ)mix,&mixerlineA,-1);
+            rc = mixerGetLineInfoA(mix, &mixerlineA, -1);
             ok(rc==MMSYSERR_INVALFLAG,
                "mixerGetLineInfoA(-1): MMSYSERR_INVALFLAG expected, got %s\n",
                mmsys_error(rc));
 
             mixerlineA.cbStruct = sizeof(mixerlineA);
             mixerlineA.dwDestination=d;
-            rc=mixerGetLineInfoA((HMIXEROBJ)mix,&mixerlineA,
-                                  MIXER_GETLINEINFOF_DESTINATION);
+            rc = mixerGetLineInfoA(mix, &mixerlineA, MIXER_GETLINEINFOF_DESTINATION);
             ok(rc==MMSYSERR_NOERROR||rc==MMSYSERR_NODRIVER,
                "mixerGetLineInfoA(MIXER_GETLINEINFOF_DESTINATION): "
                "MMSYSERR_NOERROR expected, got %s\n",
@@ -461,8 +472,7 @@ static void mixer_test_deviceA(int device)
                 mixerlineA.cbStruct = sizeof(mixerlineA);
                 mixerlineA.dwDestination=d;
                 mixerlineA.dwSource=s;
-                rc=mixerGetLineInfoA((HMIXEROBJ)mix,&mixerlineA,
-                                     MIXER_GETLINEINFOF_SOURCE);
+                rc = mixerGetLineInfoA(mix, &mixerlineA, MIXER_GETLINEINFOF_SOURCE);
                 ok(rc==MMSYSERR_NOERROR||rc==MMSYSERR_NODRIVER,
                    "mixerGetLineInfoA(MIXER_GETLINEINFOF_SOURCE): "
                    "MMSYSERR_NOERROR expected, got %s\n",
@@ -499,14 +509,13 @@ static void mixer_test_deviceA(int device)
                         if (array) {
                             memset(&controls, 0, sizeof(controls));
 
-                            rc=mixerGetLineControlsA((HMIXEROBJ)mix,0,
-                                                      MIXER_GETLINECONTROLSF_ALL);
+                            rc = mixerGetLineControlsA(mix, 0, MIXER_GETLINECONTROLSF_ALL);
                             ok(rc==MMSYSERR_INVALPARAM,
                                "mixerGetLineControlsA(MIXER_GETLINECONTROLSF_ALL): "
                                "MMSYSERR_INVALPARAM expected, got %s\n",
                                mmsys_error(rc));
 
-                            rc=mixerGetLineControlsA((HMIXEROBJ)mix,&controls,-1);
+                            rc = mixerGetLineControlsA(mix, &controls, -1);
                             ok(rc==MMSYSERR_INVALFLAG||rc==MMSYSERR_INVALPARAM,
                                "mixerGetLineControlsA(-1): "
                                "MMSYSERR_INVALFLAG or MMSYSERR_INVALPARAM expected, got %s\n",
@@ -521,8 +530,7 @@ static void mixer_test_deviceA(int device)
                             /* FIXME: do MIXER_GETLINECONTROLSF_ONEBYID
                              * and MIXER_GETLINECONTROLSF_ONEBYTYPE
                              */
-                            rc=mixerGetLineControlsA((HMIXEROBJ)mix,&controls,
-                                                     MIXER_GETLINECONTROLSF_ALL);
+                            rc = mixerGetLineControlsA(mix, &controls, MIXER_GETLINECONTROLSF_ALL);
                             ok(rc==MMSYSERR_NOERROR,
                                "mixerGetLineControlsA(MIXER_GETLINECONTROLSF_ALL): "
                                "MMSYSERR_NOERROR expected, got %s\n",
@@ -556,11 +564,11 @@ static void mixer_test_deviceA(int device)
               }
             }
         }
-        test_mixerClose(mix);
+        test_mixerClose((HMIXER)mix);
     }
 }
 
-static void mixer_test_controlW(HMIXER mix, LPMIXERCONTROLW control)
+static void mixer_test_controlW(HMIXEROBJ mix, MIXERCONTROLW *control)
 {
     MMRESULT rc;
 
@@ -577,7 +585,7 @@ static void mixer_test_controlW(HMIXER mix, LPMIXERCONTROLW control)
         details.cbDetails = sizeof(value);
 
         /* read the current control value */
-        rc=mixerGetControlDetails((HMIXEROBJ)mix,&details,MIXER_GETCONTROLDETAILSF_VALUE);
+        rc = mixerGetControlDetailsW(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE);
         ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
            "MMSYSERR_NOERROR expected, got %s\n",
            mmsys_error(rc));
@@ -600,7 +608,7 @@ static void mixer_test_controlW(HMIXER mix, LPMIXERCONTROLW control)
             new_details.cbDetails = sizeof(new_value);
 
             /* change the control value by one step */
-            rc=mixerSetControlDetails((HMIXEROBJ)mix,&new_details,MIXER_SETCONTROLDETAILSF_VALUE);
+            rc = mixerSetControlDetails(mix, &new_details, MIXER_SETCONTROLDETAILSF_VALUE);
             ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                "MMSYSERR_NOERROR expected, got %s\n",
                mmsys_error(rc));
@@ -616,7 +624,7 @@ static void mixer_test_controlW(HMIXER mix, LPMIXERCONTROLW control)
                 ret_details.cbDetails = sizeof(ret_value);
 
                 /* read back the new control value */
-                rc=mixerGetControlDetails((HMIXEROBJ)mix,&ret_details,MIXER_GETCONTROLDETAILSF_VALUE);
+                rc = mixerGetControlDetailsW(mix, &ret_details, MIXER_GETCONTROLDETAILSF_VALUE);
                 ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
                    "MMSYSERR_NOERROR expected, got %s\n",
                    mmsys_error(rc));
@@ -635,7 +643,7 @@ static void mixer_test_controlW(HMIXER mix, LPMIXERCONTROLW control)
                         details.cbDetails = sizeof(value);
 
                         /* restore original value */
-                        rc=mixerSetControlDetails((HMIXEROBJ)mix,&details,MIXER_SETCONTROLDETAILSF_VALUE);
+                        rc = mixerSetControlDetails(mix, &details, MIXER_SETCONTROLDETAILSF_VALUE);
                         ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                            "MMSYSERR_NOERROR expected, got %s\n",
                            mmsys_error(rc));
@@ -656,7 +664,7 @@ static void mixer_test_controlW(HMIXER mix, LPMIXERCONTROLW control)
         details.paDetails = &value;
         details.cbDetails = sizeof(value);
 
-        rc=mixerGetControlDetails((HMIXEROBJ)mix,&details,MIXER_GETCONTROLDETAILSF_VALUE);
+        rc = mixerGetControlDetailsW(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE);
         ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
            "MMSYSERR_NOERROR expected, got %s\n",
            mmsys_error(rc));
@@ -679,7 +687,7 @@ static void mixer_test_controlW(HMIXER mix, LPMIXERCONTROLW control)
             new_details.cbDetails = sizeof(new_value);
 
             /* change the control value by one step */
-            rc=mixerSetControlDetails((HMIXEROBJ)mix,&new_details,MIXER_SETCONTROLDETAILSF_VALUE);
+            rc = mixerSetControlDetails(mix, &new_details, MIXER_SETCONTROLDETAILSF_VALUE);
             ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                "MMSYSERR_NOERROR expected, got %s\n",
                mmsys_error(rc));
@@ -695,7 +703,7 @@ static void mixer_test_controlW(HMIXER mix, LPMIXERCONTROLW control)
                 ret_details.cbDetails = sizeof(ret_value);
 
                 /* read back the new control value */
-                rc=mixerGetControlDetails((HMIXEROBJ)mix,&ret_details,MIXER_GETCONTROLDETAILSF_VALUE);
+                rc = mixerGetControlDetailsW(mix, &ret_details, MIXER_GETCONTROLDETAILSF_VALUE);
                 ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): "
                    "MMSYSERR_NOERROR expected, got %s\n",
                    mmsys_error(rc));
@@ -714,7 +722,7 @@ static void mixer_test_controlW(HMIXER mix, LPMIXERCONTROLW control)
                         details.cbDetails = sizeof(value);
 
                         /* restore original value */
-                        rc=mixerSetControlDetails((HMIXEROBJ)mix,&details,MIXER_SETCONTROLDETAILSF_VALUE);
+                        rc = mixerSetControlDetails(mix, &details, MIXER_SETCONTROLDETAILSF_VALUE);
                         ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): "
                            "MMSYSERR_NOERROR expected, got %s\n",
                            mmsys_error(rc));
@@ -730,7 +738,7 @@ static void mixer_test_controlW(HMIXER mix, LPMIXERCONTROLW control)
 static void mixer_test_deviceW(int device)
 {
     MIXERCAPSW capsW;
-    HMIXER mix;
+    HMIXEROBJ mix;
     MMRESULT rc;
     DWORD d,s,ns,nc;
     char szShortName[MIXER_SHORT_NAME_CHARS];
@@ -767,16 +775,23 @@ static void mixer_test_deviceW(int device)
     }
 
 
-    rc=mixerOpen(&mix, device, 0, 0, 0);
+    rc = mixerOpen((HMIXER*)&mix, device, 0, 0, 0);
     ok(rc==MMSYSERR_NOERROR,
        "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",mmsys_error(rc));
     if (rc==MMSYSERR_NOERROR) {
+        MIXERCAPSW capsW2;
+
+        rc=mixerGetDevCapsW((UINT_PTR)mix,&capsW2,sizeof(capsW2));
+        ok(rc==MMSYSERR_NOERROR,
+           "mixerGetDevCapsW: MMSYSERR_NOERROR expected, got %s\n",
+           mmsys_error(rc));
+        ok(!lstrcmpW(capsW2.szPname, capsW.szPname), "Got wrong device caps\n");
+
         for (d=0;d<capsW.cDestinations;d++) {
             MIXERLINEW mixerlineW;
             mixerlineW.cbStruct = 0;
             mixerlineW.dwDestination=d;
-            rc=mixerGetLineInfoW((HMIXEROBJ)mix,&mixerlineW,
-                                 MIXER_GETLINEINFOF_DESTINATION);
+            rc = mixerGetLineInfoW(mix, &mixerlineW, MIXER_GETLINEINFOF_DESTINATION);
             ok(rc==MMSYSERR_INVALPARAM,
                "mixerGetLineInfoW(MIXER_GETLINEINFOF_DESTINATION): "
                "MMSYSERR_INVALPARAM expected, got %s\n",
@@ -784,8 +799,7 @@ static void mixer_test_deviceW(int device)
 
             mixerlineW.cbStruct = sizeof(mixerlineW);
             mixerlineW.dwDestination=capsW.cDestinations;
-            rc=mixerGetLineInfoW((HMIXEROBJ)mix,&mixerlineW,
-                                 MIXER_GETLINEINFOF_DESTINATION);
+            rc = mixerGetLineInfoW(mix, &mixerlineW, MIXER_GETLINEINFOF_DESTINATION);
             ok(rc==MMSYSERR_INVALPARAM||rc==MIXERR_INVALLINE,
                "mixerGetLineInfoW(MIXER_GETLINEINFOF_DESTINATION): "
                "MMSYSERR_INVALPARAM or MIXERR_INVALLINE expected, got %s\n",
@@ -793,8 +807,7 @@ static void mixer_test_deviceW(int device)
 
             mixerlineW.cbStruct = sizeof(mixerlineW);
             mixerlineW.dwDestination=d;
-            rc=mixerGetLineInfoW((HMIXEROBJ)mix,0,
-                                 MIXER_GETLINEINFOF_DESTINATION);
+            rc = mixerGetLineInfoW(mix, 0, MIXER_GETLINEINFOF_DESTINATION);
             ok(rc==MMSYSERR_INVALPARAM,
                "mixerGetLineInfoW(MIXER_GETLINEINFOF_DESTINATION): "
                "MMSYSERR_INVALPARAM expected, got %s\n",
@@ -802,15 +815,14 @@ static void mixer_test_deviceW(int device)
 
             mixerlineW.cbStruct = sizeof(mixerlineW);
             mixerlineW.dwDestination=d;
-            rc=mixerGetLineInfoW((HMIXEROBJ)mix,&mixerlineW,-1);
+            rc = mixerGetLineInfoW(mix, &mixerlineW, -1);
             ok(rc==MMSYSERR_INVALFLAG,
                "mixerGetLineInfoW(-1): MMSYSERR_INVALFLAG expected, got %s\n",
                mmsys_error(rc));
 
             mixerlineW.cbStruct = sizeof(mixerlineW);
             mixerlineW.dwDestination=d;
-            rc=mixerGetLineInfoW((HMIXEROBJ)mix,&mixerlineW,
-                                  MIXER_GETLINEINFOF_DESTINATION);
+            rc = mixerGetLineInfoW(mix, &mixerlineW, MIXER_GETLINEINFOF_DESTINATION);
             ok(rc==MMSYSERR_NOERROR||rc==MMSYSERR_NODRIVER,
                "mixerGetLineInfoW(MIXER_GETLINEINFOF_DESTINATION): "
                "MMSYSERR_NOERROR expected, got %s\n",
@@ -851,8 +863,7 @@ static void mixer_test_deviceW(int device)
                 mixerlineW.cbStruct = sizeof(mixerlineW);
                 mixerlineW.dwDestination=d;
                 mixerlineW.dwSource=s;
-                rc=mixerGetLineInfoW((HMIXEROBJ)mix,&mixerlineW,
-                                     MIXER_GETLINEINFOF_SOURCE);
+                rc = mixerGetLineInfoW(mix, &mixerlineW, MIXER_GETLINEINFOF_SOURCE);
                 ok(rc==MMSYSERR_NOERROR||rc==MMSYSERR_NODRIVER,
                    "mixerGetLineInfoW(MIXER_GETLINEINFOF_SOURCE): "
                    "MMSYSERR_NOERROR expected, got %s\n",
@@ -895,16 +906,14 @@ static void mixer_test_deviceW(int device)
                         array=HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,
                             mixerlineW.cControls*sizeof(MIXERCONTROLW));
                         if (array) {
-                            rc=mixerGetLineControlsW((HMIXEROBJ)mix,0,
-                                                     MIXER_GETLINECONTROLSF_ALL);
+                            rc = mixerGetLineControlsW(mix, 0, MIXER_GETLINECONTROLSF_ALL);
                             ok(rc==MMSYSERR_INVALPARAM,
                                "mixerGetLineControlsW(MIXER_GETLINECONTROLSF_ALL): "
                                "MMSYSERR_INVALPARAM expected, got %s\n",
                                mmsys_error(rc));
-                            rc=mixerGetLineControlsW((HMIXEROBJ)mix,&controls,
-                                                     -1);
+                            rc = mixerGetLineControlsW(mix, &controls, -1);
                             ok(rc==MMSYSERR_INVALFLAG||rc==MMSYSERR_INVALPARAM,
-                               "mixerGetLineControlsA(-1): "
+                               "mixerGetLineControlsW(-1): "
                                "MMSYSERR_INVALFLAG or MMSYSERR_INVALPARAM expected, got %s\n",
                                mmsys_error(rc));
 
@@ -917,8 +926,7 @@ static void mixer_test_deviceW(int device)
                             /* FIXME: do MIXER_GETLINECONTROLSF_ONEBYID
                              * and MIXER_GETLINECONTROLSF_ONEBYTYPE
                              */
-                            rc=mixerGetLineControlsW((HMIXEROBJ)mix,&controls,
-                                                     MIXER_GETLINECONTROLSF_ALL);
+                            rc = mixerGetLineControlsW(mix, &controls, MIXER_GETLINECONTROLSF_ALL);
                             ok(rc==MMSYSERR_NOERROR,
                                "mixerGetLineControlsW(MIXER_GETLINECONTROLSF_ALL): "
                                "MMSYSERR_NOERROR expected, got %s\n",
@@ -955,7 +963,7 @@ static void mixer_test_deviceW(int device)
                 }
             }
         }
-        test_mixerClose(mix);
+        test_mixerClose((HMIXER)mix);
     }
 }
 
@@ -1004,6 +1012,7 @@ static void mixer_testsW(void)
 static void test_mixerOpen(void)
 {
     HMIXER mix;
+    HANDLE event;
     MMRESULT rc;
     UINT ndev, d;
 
@@ -1031,7 +1040,7 @@ static void test_mixerOpen(void)
 
         rc = mixerOpen(&mix, d, 0xdeadbeef, 0, CALLBACK_WINDOW);
         ok(rc == MMSYSERR_INVALPARAM ||
-           rc == MMSYSERR_NOERROR, /* 98 */
+           broken(rc == MMSYSERR_NOERROR /* 98 */),
            "mixerOpen: MMSYSERR_INVALPARAM expected, got %s\n",
            mmsys_error(rc));
         if (rc == MMSYSERR_NOERROR)
@@ -1042,7 +1051,34 @@ static void test_mixerOpen(void)
         ok(rc == MMSYSERR_NOERROR,
            "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",
            mmsys_error(rc));
+        if (rc == MMSYSERR_NOERROR)
+            test_mixerClose(mix);
 
+        rc = mixerOpen(&mix, d, 0, 0, CALLBACK_THREAD);
+        ok(rc == MMSYSERR_NOERROR /* since w2k */ ||
+           rc == MMSYSERR_NOTSUPPORTED, /* 98 */
+           "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",
+           mmsys_error(rc));
+        if (rc == MMSYSERR_NOERROR)
+            test_mixerClose(mix);
+
+        rc = mixerOpen(&mix, d, 0, 0, CALLBACK_EVENT);
+        ok(rc == MMSYSERR_NOERROR /* since w2k */ ||
+           rc == MMSYSERR_NOTSUPPORTED, /* 98 */
+           "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",
+           mmsys_error(rc));
+        if (rc == MMSYSERR_NOERROR)
+            test_mixerClose(mix);
+
+        event = CreateEventW(NULL, FALSE, FALSE, NULL);
+
+        /* NOTSUPPORTED is not broken, but it enables the todo_wine marker. */
+        rc = mixerOpen(&mix, d, (DWORD_PTR)event, 0, CALLBACK_EVENT);
+        todo_wine
+        ok(rc == MMSYSERR_NOERROR /* since w2k */ ||
+           broken(rc == MMSYSERR_NOTSUPPORTED), /* 98 */
+           "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",
+           mmsys_error(rc));
         if (rc == MMSYSERR_NOERROR)
             test_mixerClose(mix);
 
@@ -1054,6 +1090,10 @@ static void test_mixerOpen(void)
 
         if (rc == MMSYSERR_NOERROR)
             test_mixerClose(mix);
+
+        rc = WaitForSingleObject(event, 0);
+        ok(rc == WAIT_TIMEOUT, "WaitEvent %d\n", rc);
+        CloseHandle(event);
     }
 }
 
index 79ae031..cf02c0d 100644 (file)
@@ -18,7 +18,6 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
 
-#include <assert.h>
 #include <stdarg.h>
 
 #include "windef.h"
@@ -45,42 +44,65 @@ static DWORD RIFF_buf[] =
     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 expect_buf_offset_dbg(HMMIO hmmio, LONG off, int line)
+{
+    MMIOINFO mmio;
+    LONG ret;
+
+    memset(&mmio, 0, sizeof(mmio));
+    ret = mmioGetInfo(hmmio, &mmio, 0);
+    ok_(__FILE__, line)(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret);
+    ok_(__FILE__, line)(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok_(__FILE__, line)(ret == off, "expected %d, got %d\n", off, ret);
+}
+
+#define expect_buf_offset(a1, a2) expect_buf_offset_dbg(a1, a2, __LINE__)
+
 static void test_mmioDescend(char *fname)
 {
     MMRESULT ret;
     HMMIO hmmio;
     MMIOINFO mmio;
-    MMCKINFO ckRiff, ckList, ck;
+    MMCKINFO ckRiff, ckList, ck, ckList2;
 
     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);
+    hmmio = mmioOpenA(fname, &mmio, MMIO_READ);
     if (fname && !hmmio)
     {
-        skip("%s file is missing, skipping the test\n", fname);
+        trace("No optional %s file. Skipping the test\n", fname);
         return;
     }
-    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
+
+    expect_buf_offset(hmmio, 0);
 
     /* 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);
+    ok(ckRiff.dwDataOffset == 8, "expected 8 got %u\n", ckRiff.dwDataOffset);
     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);
 
+    expect_buf_offset(hmmio, 12);
+
     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);
+    ok(ckList.dwDataOffset == 20, "expected 20 got %u\n", ckList.dwDataOffset);
     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);
 
+    expect_buf_offset(hmmio, 24);
+
     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);
@@ -89,18 +111,23 @@ static void test_mmioDescend(char *fname)
           (LPCSTR)&ck.ckid, ck.cksize, (LPCSTR)&ck.fccType,
           ck.dwDataOffset, ck.dwFlags);
 
+    expect_buf_offset(hmmio, 32);
+
     /* Skip chunk data */
-    mmioSeek(hmmio, ck.cksize, SEEK_CUR);
+    ret = mmioSeek(hmmio, ck.cksize, SEEK_CUR);
+    ok(ret == 0x58, "expected 0x58, got %#x\n", ret);
 
-    ret = mmioDescend(hmmio, &ckList, &ckList, 0);
+    ret = mmioDescend(hmmio, &ckList2, &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);
+    ok(ckList2.ckid == FOURCC_LIST, "wrong ckid: %04x\n", ckList2.ckid);
+    ok(ckList2.fccType == listtypeSTREAMHEADER, "wrong fccType: %04x\n", ckList2.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);
+          (LPCSTR)&ckList2.ckid, ckList2.cksize, (LPCSTR)&ckList2.fccType,
+          ckList2.dwDataOffset, ckList2.dwFlags);
 
-    ret = mmioDescend(hmmio, &ck, &ckList, 0);
+    expect_buf_offset(hmmio, 100);
+
+    ret = mmioDescend(hmmio, &ck, &ckList2, 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);
@@ -108,6 +135,8 @@ static void test_mmioDescend(char *fname)
           (LPCSTR)&ck.ckid, ck.cksize, (LPCSTR)&ck.fccType,
           ck.dwDataOffset, ck.dwFlags);
 
+    expect_buf_offset(hmmio, 108);
+
     /* test various mmioDescend flags */
 
     mmioSeek(hmmio, 0, SEEK_SET);
@@ -199,7 +228,7 @@ static void test_mmioDescend(char *fname)
 
 static void test_mmioOpen(char *fname)
 {
-    char buf[256];
+    char buf[MMIO_DEFAULTBUFFER];
     MMRESULT ret;
     HMMIO hmmio;
     MMIOINFO mmio;
@@ -208,13 +237,13 @@ static void test_mmioOpen(char *fname)
     mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
     mmio.cchBuffer = sizeof(buf);
     mmio.pchBuffer = buf;
-    hmmio = mmioOpen(fname, &mmio, MMIO_READ);
+    hmmio = mmioOpenA(fname, &mmio, MMIO_READ);
     if (fname && !hmmio)
     {
-        skip("%s file is missing, skipping the test\n", fname);
+        trace("No optional %s file. Skipping the test\n", fname);
         return;
     }
-    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
 
     memset(&mmio, 0, sizeof(mmio));
     ret = mmioGetInfo(hmmio, &mmio, 0);
@@ -224,6 +253,17 @@ static void test_mmioOpen(char *fname)
     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);
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    if (mmio.fccIOProc == FOURCC_DOS)
+        ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+    else
+        ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     mmioClose(hmmio, 0);
 
@@ -231,8 +271,8 @@ static void test_mmioOpen(char *fname)
     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);
+    hmmio = mmioOpenA(fname, &mmio, MMIO_READ);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
 
     memset(&mmio, 0, sizeof(mmio));
     ret = mmioGetInfo(hmmio, &mmio, 0);
@@ -242,6 +282,14 @@ static void test_mmioOpen(char *fname)
     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);
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     mmioClose(hmmio, 0);
 
@@ -249,8 +297,8 @@ static void test_mmioOpen(char *fname)
     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);
+    hmmio = mmioOpenA(fname, &mmio, MMIO_READ);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
 
     memset(&mmio, 0, sizeof(mmio));
     ret = mmioGetInfo(hmmio, &mmio, 0);
@@ -260,6 +308,14 @@ static void test_mmioOpen(char *fname)
     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");
+    ok(mmio.pchNext == NULL, "expected NULL\n");
+    ok(mmio.pchEndRead == NULL, "expected NULL\n");
+    ok(mmio.pchEndWrite == NULL, "expected NULL\n");
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     mmioClose(hmmio, 0);
 
@@ -267,8 +323,8 @@ static void test_mmioOpen(char *fname)
     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);
+    hmmio = mmioOpenA(fname, &mmio, MMIO_READ);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
 
     memset(&mmio, 0, sizeof(mmio));
     ret = mmioGetInfo(hmmio, &mmio, 0);
@@ -278,6 +334,17 @@ static void test_mmioOpen(char *fname)
     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");
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    if (mmio.fccIOProc == FOURCC_DOS)
+        ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+    else
+        ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     mmioClose(hmmio, 0);
 
@@ -285,8 +352,8 @@ static void test_mmioOpen(char *fname)
     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);
+    hmmio = mmioOpenA(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
 
     memset(&mmio, 0, sizeof(mmio));
     ret = mmioGetInfo(hmmio, &mmio, 0);
@@ -296,6 +363,17 @@ static void test_mmioOpen(char *fname)
     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);
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    if (mmio.fccIOProc == FOURCC_DOS)
+        ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+    else
+        ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     mmioClose(hmmio, 0);
 
@@ -303,8 +381,8 @@ static void test_mmioOpen(char *fname)
     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);
+    hmmio = mmioOpenA(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
 
     memset(&mmio, 0, sizeof(mmio));
     ret = mmioGetInfo(hmmio, &mmio, 0);
@@ -314,6 +392,17 @@ static void test_mmioOpen(char *fname)
     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");
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    if (mmio.fccIOProc == FOURCC_DOS)
+        ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+    else
+        ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     mmioClose(hmmio, 0);
 
@@ -321,8 +410,8 @@ static void test_mmioOpen(char *fname)
     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);
+    hmmio = mmioOpenA(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
 
     memset(&mmio, 0, sizeof(mmio));
     ret = mmioGetInfo(hmmio, &mmio, 0);
@@ -332,6 +421,17 @@ static void test_mmioOpen(char *fname)
     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");
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    if (mmio.fccIOProc == FOURCC_DOS)
+        ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+    else
+        ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     mmioClose(hmmio, 0);
 
@@ -339,14 +439,14 @@ static void test_mmioOpen(char *fname)
     mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
     mmio.cchBuffer = 0;
     mmio.pchBuffer = buf;
-    hmmio = mmioOpen(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF);
+    hmmio = mmioOpenA(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);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
 
     memset(&mmio, 0, sizeof(mmio));
     ret = mmioGetInfo(hmmio, &mmio, 0);
@@ -356,6 +456,17 @@ static void test_mmioOpen(char *fname)
     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);
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    if (mmio.fccIOProc == FOURCC_DOS)
+        ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+    else
+        ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     mmioClose(hmmio, 0);
 }
@@ -371,13 +482,13 @@ static void test_mmioSetBuffer(char *fname)
     mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM;
     mmio.cchBuffer = sizeof(buf);
     mmio.pchBuffer = buf;
-    hmmio = mmioOpen(fname, &mmio, MMIO_READ);
+    hmmio = mmioOpenA(fname, &mmio, MMIO_READ);
     if (fname && !hmmio)
     {
-        skip("%s file is missing, skipping the test\n", fname);
+        trace("No optional %s file. Skipping the test\n", fname);
         return;
     }
-    ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
 
     memset(&mmio, 0, sizeof(mmio));
     ret = mmioGetInfo(hmmio, &mmio, 0);
@@ -387,6 +498,17 @@ static void test_mmioSetBuffer(char *fname)
     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);
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    if (mmio.fccIOProc == FOURCC_DOS)
+        ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+    else
+        ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     ret = mmioSetBuffer(hmmio, NULL, 0, 0);
     ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret);
@@ -399,6 +521,14 @@ static void test_mmioSetBuffer(char *fname)
     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");
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     ret = mmioSetBuffer(hmmio, NULL, 0, MMIO_ALLOCBUF);
     ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret);
@@ -411,6 +541,14 @@ static void test_mmioSetBuffer(char *fname)
     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");
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     ret = mmioSetBuffer(hmmio, buf, 0, MMIO_ALLOCBUF);
     ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret);
@@ -423,6 +561,14 @@ static void test_mmioSetBuffer(char *fname)
     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);
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
 
     ret = mmioSetBuffer(hmmio, NULL, 256, MMIO_WRITE|MMIO_ALLOCBUF);
     ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret);
@@ -436,12 +582,336 @@ static void test_mmioSetBuffer(char *fname)
     ok(mmio.cchBuffer == 256, "got %u\n", mmio.cchBuffer);
     ok(mmio.pchBuffer != NULL, "expected not NULL\n");
     ok(mmio.pchBuffer != buf, "expected != buf\n");
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", buf, mmio.pchEndRead);
+    ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset);
+
+    ret = mmioSeek(hmmio, 0, SEEK_CUR);
+    ok(ret == 0, "expected 0, got %d\n", ret);
+
+    mmioClose(hmmio, 0);
+}
+
+#define FOURCC_XYZ mmioFOURCC('X', 'Y', 'Z', ' ')
+
+static LRESULT CALLBACK mmio_test_IOProc(LPSTR lpMMIOInfo, UINT uMessage, LPARAM lParam1, LPARAM lParam2)
+{
+    LPMMIOINFO lpInfo = (LPMMIOINFO) lpMMIOInfo;
+
+    switch (uMessage)
+    {
+    case MMIOM_OPEN:
+        if (lpInfo->fccIOProc == FOURCC_DOS)
+            lpInfo->fccIOProc = mmioFOURCC('F', 'A', 'I', 'L');
+        return MMSYSERR_NOERROR;
+    case MMIOM_CLOSE:
+        return MMSYSERR_NOERROR;
+    case MMIOM_SEEK:
+        lpInfo->adwInfo[1]++;
+        lpInfo->lDiskOffset = 0xdeadbeef;
+        return 0;
+    default:
+        return 0;
+    }
+}
+
+static void test_mmioOpen_fourcc(void)
+{
+    char fname[] = "file+name.xyz+one.two";
+
+    LPMMIOPROC lpProc;
+    HMMIO hmmio;
+    MMIOINFO mmio;
+
+    lpProc = mmioInstallIOProcA(FOURCC_DOS, mmio_test_IOProc, MMIO_INSTALLPROC);
+    ok(lpProc == mmio_test_IOProc, "mmioInstallIOProcA error\n");
+
+    lpProc = mmioInstallIOProcA(FOURCC_XYZ, mmio_test_IOProc, MMIO_INSTALLPROC);
+    ok(lpProc == mmio_test_IOProc, "mmioInstallIOProcA error\n");
+
+    memset(&mmio, 0, sizeof(mmio));
+    hmmio = mmioOpenA(fname, &mmio, MMIO_READ);
+    mmioGetInfo(hmmio, &mmio, 0);
+    ok(hmmio && mmio.fccIOProc == FOURCC_XYZ, "mmioOpenA error %u, got %4.4s\n",
+            mmio.wErrorRet, (LPCSTR)&mmio.fccIOProc);
+    ok(mmio.adwInfo[1] == 0, "mmioOpenA sent MMIOM_SEEK, got %d\n",
+       mmio.adwInfo[1]);
+    ok(mmio.lDiskOffset == 0, "mmioOpenA updated lDiskOffset, got %d\n",
+       mmio.lDiskOffset);
+    mmioClose(hmmio, 0);
+
+    mmioInstallIOProcA(FOURCC_XYZ, NULL, MMIO_REMOVEPROC);
+
+    memset(&mmio, 0, sizeof(mmio));
+    hmmio = mmioOpenA(fname, &mmio, MMIO_READ);
+    mmioGetInfo(hmmio, &mmio, 0);
+    ok(!hmmio && mmio.wErrorRet == MMIOERR_FILENOTFOUND, "mmioOpenA error %u, got %4.4s\n",
+            mmio.wErrorRet, (LPCSTR)&mmio.fccIOProc);
+    mmioClose(hmmio, 0);
+
+    mmioInstallIOProcA(FOURCC_DOS, NULL, MMIO_REMOVEPROC);
+}
+
+static BOOL create_test_file(char *temp_file)
+{
+    char temp_path[MAX_PATH];
+    DWORD ret, written;
+    HANDLE h;
+
+    ret = GetTempPathA(sizeof(temp_path), temp_path);
+    ok(ret, "Failed to get a temp path, err %d\n", GetLastError());
+    if (!ret)
+        return FALSE;
+
+    ret = GetTempFileNameA(temp_path, "mmio", 0, temp_file);
+    ok(ret, "Failed to get a temp name, err %d\n", GetLastError());
+    if (!ret)
+        return FALSE;
+
+    h = CreateFileA(temp_file, GENERIC_WRITE, 0, NULL,
+                    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+    ok(h != INVALID_HANDLE_VALUE, "Failed to create a file, err %d\n", GetLastError());
+    if (h == INVALID_HANDLE_VALUE) return FALSE;
+
+    ret = WriteFile(h, RIFF_buf, sizeof(RIFF_buf), &written, NULL);
+    ok(ret, "Failed to write a file, err %d\n", GetLastError());
+    CloseHandle(h);
+    if (!ret) DeleteFileA(temp_file);
+    return ret;
+}
+
+static void test_mmioSeek(void)
+{
+    HMMIO hmmio;
+    MMIOINFO mmio;
+    LONG end, pos;
+    const LONG size = sizeof(RIFF_buf), offset = 16;
+    char test_file[MAX_PATH];
+    MMRESULT res;
+    HFILE hfile;
+    OFSTRUCT ofs;
+
+    /* test memory file */
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = FOURCC_MEM;
+    mmio.pchBuffer = (char*)&RIFF_buf;
+    mmio.cchBuffer = sizeof(RIFF_buf);
+    hmmio = mmioOpenA(NULL, &mmio, MMIO_READ);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
+    if (hmmio != NULL) {
+        /* seek to the end */
+        end = mmioSeek(hmmio, 0, SEEK_END);
+        ok(end == size, "expected %d, got %d\n", size, end);
+
+        /* test MMIOINFO values */
+        res = mmioGetInfo(hmmio, &mmio, 0);
+        ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res);
+        ok(mmio.pchNext == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchNext);
+        ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead);
+        ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite);
+        ok(mmio.lBufOffset == 0, "expected %d, got %d\n", 0, mmio.lBufOffset);
+        ok(mmio.lDiskOffset == 0, "expected %d, got %d\n", 0, mmio.lDiskOffset);
+
+        /* seek backward from the end */
+        pos = mmioSeek(hmmio, offset, SEEK_END);
+        ok(pos == size-offset, "expected %d, got %d\n", size-offset, pos);
+
+        mmioClose(hmmio, 0);
+    }
+
+    if (!create_test_file(test_file)) return;
+
+    /* test standard file without buffering */
+    hmmio = NULL;
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = FOURCC_DOS;
+    mmio.pchBuffer = 0;
+    mmio.cchBuffer = 0;
+    hmmio = mmioOpenA(test_file, &mmio, MMIO_READ);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
+    if (hmmio != NULL) {
+        /* seek to the end */
+        end = mmioSeek(hmmio, 0, SEEK_END);
+        ok(end == size, "expected %d, got %d\n", size, end);
+
+        /* test MMIOINFO values */
+        res = mmioGetInfo(hmmio, &mmio, 0);
+        ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res);
+        ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+        ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+        ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite);
+        ok(mmio.lBufOffset == size, "expected %d, got %d\n", size, mmio.lBufOffset);
+        ok(mmio.lDiskOffset == size, "expected %d, got %d\n", size, mmio.lDiskOffset);
+
+        /* seek backward from the end */
+        pos = mmioSeek(hmmio, offset, SEEK_END);
+        ok(pos == size-offset, "expected %d, got %d\n", size-offset, pos);
+
+        mmioClose(hmmio, 0);
+    }
+
+    /* test standard file with buffering */
+    hmmio = NULL;
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = FOURCC_DOS;
+    mmio.pchBuffer = 0;
+    mmio.cchBuffer = 0;
+    hmmio = mmioOpenA(test_file, &mmio, MMIO_READ | MMIO_ALLOCBUF);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
+    if (hmmio != NULL) {
+        /* seek to the end */
+        end = mmioSeek(hmmio, 0, SEEK_END);
+        ok(end == size, "expected %d, got %d\n", size, end);
+
+        /* test MMIOINFO values */
+        res = mmioGetInfo(hmmio, &mmio, 0);
+        ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res);
+        ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+        ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+        ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite);
+        ok(mmio.lBufOffset == end, "expected %d, got %d\n", end, mmio.lBufOffset);
+        ok(mmio.lDiskOffset == size, "expected %d, got %d\n", size, mmio.lDiskOffset);
+
+        /* seek backward from the end */
+        pos = mmioSeek(hmmio, offset, SEEK_END);
+        ok(pos == size-offset, "expected %d, got %d\n", size-offset, pos);
+
+        mmioClose(hmmio, 0);
+    }
+
+    /* test seek position inheritance from standard file handle */
+    hfile = OpenFile(test_file, &ofs, OF_READ);
+    ok(hfile != HFILE_ERROR, "Failed to open the file, err %d\n", GetLastError());
+    if (hfile != HFILE_ERROR) {
+        pos = _llseek(hfile, offset, SEEK_SET);
+        ok(pos != HFILE_ERROR, "Failed to seek, err %d\n", GetLastError());
+        memset(&mmio, 0, sizeof(mmio));
+        mmio.fccIOProc = FOURCC_DOS;
+        mmio.adwInfo[0] = (DWORD)hfile;
+        hmmio = mmioOpenA(NULL, &mmio, MMIO_READ | MMIO_DENYWRITE | MMIO_ALLOCBUF);
+        ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
+        if (hmmio != NULL) {
+            pos = mmioSeek(hmmio, 0, SEEK_CUR);
+            ok(pos == offset, "expected %d, got %d\n", offset, pos);
+            mmioClose(hmmio, 0);
+        }
+    }
+
+    DeleteFileA(test_file);
+}
+
+static void test_mmio_end_of_file(void)
+{
+    char test_file[MAX_PATH], buffer[128], data[16];
+    MMIOINFO mmio;
+    HMMIO hmmio;
+    LONG ret;
+    MMRESULT res;
+
+    if (!create_test_file(test_file)) return;
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = FOURCC_DOS;
+    mmio.pchBuffer = buffer;
+    mmio.cchBuffer = sizeof(buffer);
+    hmmio = mmioOpenA(test_file, &mmio, MMIO_READ);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
+    if (hmmio == NULL) {
+        DeleteFileA(test_file);
+        return;
+    }
+
+    ret = mmioSeek(hmmio, 0, SEEK_END);
+    ok(sizeof(RIFF_buf) == ret, "got %d\n", ret);
+
+    ret = mmioRead(hmmio, data, sizeof(data));
+    ok(ret == 0, "expected %d, got %d\n", 0, ret);
+
+    res = mmioGetInfo(hmmio, &mmio, 0);
+    ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res);
+
+    res = mmioAdvance(hmmio, &mmio, MMIO_READ);
+    ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res);
+    ok(mmio.pchNext == mmio.pchEndRead, "expected %p, got %p\n", mmio.pchEndRead, mmio.pchNext);
+
+    mmioClose(hmmio, 0);
+    DeleteFileA(test_file);
+}
+
+static void test_mmio_buffer_pointer(void)
+{
+    char test_file[MAX_PATH];
+    char buffer[5], data[16];
+    MMIOINFO mmio;
+    HMMIO hmmio;
+    LONG size, pos;
+    MMRESULT res;
+
+    if (!create_test_file(test_file)) return;
+
+    memset(&mmio, 0, sizeof(mmio));
+    mmio.fccIOProc = FOURCC_DOS;
+    mmio.pchBuffer = buffer;
+    mmio.cchBuffer = sizeof(buffer);
+    hmmio = mmioOpenA(test_file, &mmio, MMIO_READ);
+    ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet);
+    if (hmmio == NULL) {
+        DeleteFileA(test_file);
+        return;
+    }
+
+    /* the buffer is empty */
+    size = mmioRead(hmmio, data, 0);
+    ok(size == 0, "expected 0, got %d\n", size);
+    res = mmioGetInfo(hmmio, &mmio, 0);
+    ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res);
+    ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+
+    /* fill the buffer */
+    size = mmioAdvance(hmmio, &mmio, MMIO_READ);
+    ok(mmio.pchEndRead-mmio.pchBuffer == sizeof(buffer), "got %d\n", (int)(mmio.pchEndRead-mmio.pchBuffer));
+
+    /* seeking to the same buffer chunk, the buffer is kept */
+    size = sizeof(buffer)/2;
+    pos = mmioSeek(hmmio, size, SEEK_SET);
+    ok(pos == size, "failed to seek, expected %d, got %d\n", size, pos);
+    res = mmioGetInfo(hmmio, &mmio, 0);
+    ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res);
+    ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset);
+    ok(mmio.pchNext-mmio.pchBuffer == size, "expected %d, got %d\n", size, (int)(mmio.pchNext-mmio.pchBuffer));
+    ok(mmio.pchEndRead-mmio.pchBuffer == sizeof(buffer), "got %d\n", (int)(mmio.pchEndRead-mmio.pchBuffer));
+
+    /* seeking to another buffer chunk, the buffer is empty */
+    size = sizeof(buffer) * 3 + sizeof(buffer) / 2;
+    pos = mmioSeek(hmmio, size, SEEK_SET);
+    ok(pos == size, "failed to seek, got %d\n", pos);
+    res = mmioGetInfo(hmmio, &mmio, 0);
+    ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res);
+    ok(mmio.lBufOffset == size, "expected %d, got %d\n", size, mmio.lBufOffset);
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
+
+    /* reading a lot (as sizeof(data) > mmio.cchBuffer), the buffer is empty */
+    size = mmioRead(hmmio, data, sizeof(data));
+    ok(size == sizeof(data), "failed to read, got %d\n", size);
+    res = mmioGetInfo(hmmio, &mmio, 0);
+    ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res);
+    ok(mmio.lBufOffset == pos+size, "expected %d, got %d\n", pos+size, mmio.lBufOffset);
+    ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext);
+    ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead);
 
     mmioClose(hmmio, 0);
+    DeleteFileA(test_file);
 }
 
 START_TEST(mmio)
 {
+    /* Make it possible to run the tests against a specific AVI file in
+     * addition to the builtin test data. This is mostly meant as a
+     * debugging aid and is not part of the standard tests.
+     */
     char fname[] = "msrle.avi";
 
     test_mmioDescend(NULL);
@@ -450,4 +920,8 @@ START_TEST(mmio)
     test_mmioOpen(fname);
     test_mmioSetBuffer(NULL);
     test_mmioSetBuffer(fname);
+    test_mmioOpen_fourcc();
+    test_mmioSeek();
+    test_mmio_end_of_file();
+    test_mmio_buffer_pointer();
 }
index e61d92d..1675894 100644 (file)
@@ -1,13 +1,13 @@
 /* Automatically generated file; DO NOT EDIT!! */
 
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-
 #define STANDALONE
-#include "wine/test.h"
+#include <wine/test.h>
 
 extern void func_capture(void);
+extern void func_joystick(void);
 extern void func_mci(void);
+extern void func_mcicda(void);
+extern void func_midi(void);
 extern void func_mixer(void);
 extern void func_mmio(void);
 extern void func_timer(void);
@@ -16,7 +16,10 @@ extern void func_wave(void);
 const struct test winetest_testlist[] =
 {
     { "capture", func_capture },
+    { "joystick", func_joystick },
     { "mci", func_mci },
+    { "mcicda", func_mcicda },
+    { "midi", func_midi },
     { "mixer", func_mixer },
     { "mmio", func_mmio },
     { "timer", func_timer },
index 7ac86b9..be63f6b 100644 (file)
@@ -190,6 +190,7 @@ static void test_priority(void)
            "thread priority is %s, should be THREAD_PRIORITY_TIME_CRITICAL\n",
            get_priority(priority));
     }
+    timeKillEvent(id);
 }
 
 START_TEST(timer)
index 6a4786a..fc2bab0 100644 (file)
 #include "winnls.h"
 #include "mmsystem.h"
 #define NOBITMAP
+#include "mmddk.h"
 #include "mmreg.h"
-#include "ks.h"
-#include "ksmedia.h"
+//#include "ks.h"
+//#include "ksguid.h"
+//#include "ksmedia.h"
 
 #include "winmm_test.h"
 
+/* FIXME */
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
+DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
+
+static DWORD g_tid;
+
 static void test_multiple_waveopens(void)
 {
     HWAVEOUT handle1, handle2;
@@ -262,10 +270,9 @@ const char* wave_out_error(MMRESULT error)
     static char long_msg[1100];
     MMRESULT rc;
 
-    rc = waveOutGetErrorText(error, msg, sizeof(msg));
+    rc = waveOutGetErrorTextA(error, msg, sizeof(msg));
     if (rc != MMSYSERR_NOERROR)
-        sprintf(long_msg, "waveOutGetErrorText(%x) failed with error %x",
-                error, rc);
+        sprintf(long_msg, "waveOutGetErrorTextA(%x) failed with error %x", error, rc);
     else
         sprintf(long_msg, "%s(%s)", mmsys_error(error), msg);
     return long_msg;
@@ -274,7 +281,7 @@ const char* wave_out_error(MMRESULT error)
 const char * wave_open_flags(DWORD flags)
 {
     static char msg[1024];
-    int first = TRUE;
+    BOOL first = TRUE;
     msg[0] = 0;
     if ((flags & CALLBACK_TYPEMASK) == CALLBACK_EVENT) {
         strcat(msg, "CALLBACK_EVENT");
@@ -318,7 +325,6 @@ const char * wave_open_flags(DWORD flags)
     if ((flags & WAVE_MAPPED) == WAVE_MAPPED) {
         if (!first) strcat(msg, "|");
         strcat(msg, "WAVE_MAPPED");
-        first = FALSE;
     }
     return msg;
 }
@@ -327,7 +333,7 @@ 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;
+    BOOL first = TRUE;
     msg[0] = 0;
     if (flags & WHDR_BEGINLOOP) {
         strcat(msg, "WHDR_BEGINLOOP");
@@ -468,10 +474,10 @@ DWORD time_to_bytes(LPMMTIME mmtime, LPWAVEFORMATEX pwfx)
     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;
+        return ((mmtime->u.smpte.hour * 60 * 60) +
+                (mmtime->u.smpte.min * 60) +
+                (mmtime->u.smpte.sec)) * pwfx->nAvgBytesPerSec +
+                mmtime->u.smpte.frame  * pwfx->nAvgBytesPerSec / 30;
 
     trace("FIXME: time_to_bytes() type not supported\n");
     return -1;
@@ -481,16 +487,16 @@ 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) - 1);
+    ok(rc==MMSYSERR_ERROR,
+       "waveOutGetPosition(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
 
     mmtime.wType = TIME_BYTES;
-    rc=waveOutGetPosition(wout, &mmtime, sizeof(mmtime));
+    rc=waveOutGetPosition(wout, &mmtime, sizeof(mmtime) + 1);
     ok(rc==MMSYSERR_NOERROR,
        "waveOutGetPosition(%s): rc=%s\n",dev_name(device),wave_out_error(rc));
     if (mmtime.wType != TIME_BYTES && winetest_debug > 1)
@@ -564,6 +570,8 @@ static void CALLBACK callback_func(HWAVEOUT hwo, UINT uMsg,
                                    DWORD_PTR dwInstance,
                                    DWORD dwParam1, DWORD dwParam2)
 {
+    if(uMsg == WOM_OPEN || uMsg == WOM_CLOSE)
+        ok(GetCurrentThreadId() == g_tid, "Got different thread ID\n");
     SetEvent((HANDLE)dwInstance);
 }
 
@@ -574,12 +582,12 @@ static DWORD WINAPI callback_thread(LPVOID lpParameter)
     PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE );  /* make sure the thread has a message queue */
     SetEvent(lpParameter);
 
-    while (GetMessage(&msg, 0, 0, 0)) {
+    while (GetMessageA(&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);
+            "GetMessageA returned unexpected message: %u\n", message);
         if (message == WOM_OPEN || message == WOM_DONE || message == WOM_CLOSE)
             SetEvent(lpParameter);
         else if (message == WM_APP) {
@@ -591,21 +599,19 @@ static DWORD WINAPI callback_thread(LPVOID lpParameter)
     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)
+static void wave_out_test_deviceOut(int device, double duration, int headers, int loops,
+        WAVEFORMATEX *pwfx, DWORD format, DWORD flags, WAVEOUTCAPSA *pcaps, BOOL interactive,
+        BOOL sine, BOOL pause)
 {
     HWAVEOUT wout;
-    HANDLE hevent;
+    HANDLE hevent = CreateEventW(NULL, FALSE, FALSE, NULL);
     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;
+    BOOL has_volume = (pcaps->dwSupport & WAVECAPS_VOLUME) != 0;
     double paused = 0.0;
     DWORD_PTR callback = 0;
     DWORD_PTR callback_instance = 0;
@@ -616,11 +622,6 @@ static void wave_out_test_deviceOut(int device, double duration,
     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;
@@ -653,6 +654,7 @@ static void wave_out_test_deviceOut(int device, double duration,
         return;
     }
     wout=NULL;
+    g_tid = GetCurrentThreadId();
     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 */
@@ -665,13 +667,13 @@ static void wave_out_test_deviceOut(int device, double duration,
        (!(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",
+       "waveOutOpen(%s): format=%dx%2dx%d flags=%x(%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 "
+        trace(" Reason: The device lists this format as supported in its "
               "capabilities but opening it failed.\n");
     if ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) &&
        !(pcaps->dwFormats & format))
@@ -683,7 +685,8 @@ static void wave_out_test_deviceOut(int device, double duration,
     if (rc!=MMSYSERR_NOERROR)
         goto EXIT;
 
-    WaitForSingleObject(hevent,10000);
+    rc=WaitForSingleObject(hevent,9000);
+    ok(rc==WAIT_OBJECT_0, "missing WOM_OPEN notification\n");
 
     ok(pwfx->nChannels==nChannels &&
        pwfx->wBitsPerSample==wBitsPerSample &&
@@ -726,9 +729,8 @@ static void wave_out_test_deviceOut(int device, double duration,
     }
 
     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,
+              sine ? "440 Hz tone" : "silence", pwfx->nSamplesPerSec,
               pwfx->wBitsPerSample,pwfx->nChannels, headers, headers > 1 ? "s": " ",
               loops, loops == 1 ? " " : "s", length * (loops + 1),
               get_format_str(pwfx->wFormatTag),
@@ -747,8 +749,6 @@ static void wave_out_test_deviceOut(int device, double duration,
         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));
@@ -789,7 +789,8 @@ static void wave_out_test_deviceOut(int device, double duration,
                     ok(rc==MMSYSERR_NOERROR,"waveOutWrite(%s, header[%d]): rc=%s\n",
                        dev_name(device),(i+1)%headers,wave_out_error(rc));
                 }
-                WaitForSingleObject(hevent,10000);
+                rc=WaitForSingleObject(hevent,8000);
+                ok(rc==WAIT_OBJECT_0, "missing WOM_DONE notification\n");
             }
         }
 
@@ -809,15 +810,65 @@ static void wave_out_test_deviceOut(int device, double duration,
            "waveOutUnprepareHeader(%s): rc=%s\n",dev_name(device),
            wave_out_error(rc));
     }
-    HeapFree(GetProcessHeap(), 0, buffer);
+
+    ok(frags[0].dwFlags==(interactive ? WHDR_DONE : 0), "dwFlags(%d)=%x\n",device,frags[0].dwFlags);
+
+    frags[0].dwFlags |= WHDR_DONE;
+    rc=waveOutUnprepareHeader(wout, &frags[0], sizeof(frags[0]));
+    ok(rc==MMSYSERR_NOERROR, "waveOutUnprepareHeader(%d): rc=%s\n",device,wave_out_error(rc));
+    ok(frags[0].dwFlags==WHDR_DONE, "dwFlags(%d)=%x\n",device,frags[0].dwFlags);
+
+    frags[0].dwFlags |= WHDR_INQUEUE;
+    rc=waveOutPrepareHeader(wout, &frags[0], sizeof(frags[0]));
+    ok(rc==MMSYSERR_NOERROR, "waveOutPrepareHeader(%d): rc=%s\n",device,wave_out_error(rc));
+    ok(frags[0].dwFlags==WHDR_PREPARED, "dwFlags(%d)=%x\n",device,frags[0].dwFlags);
+
+    frags[0].dwFlags |= WHDR_INQUEUE;
+    rc=waveOutPrepareHeader(wout, &frags[0], sizeof(frags[0]));
+    ok(rc==MMSYSERR_NOERROR, "waveOutPrepareHeader(%d): rc=%s\n",device,wave_out_error(rc));
+    ok(frags[0].dwFlags==(WHDR_PREPARED|WHDR_INQUEUE), "dwFlags(%d)=%x\n",device,frags[0].dwFlags);
+
+    frags[0].dwFlags &= ~(WHDR_INQUEUE|WHDR_DONE);
+    rc=waveOutUnprepareHeader(wout, &frags[0], sizeof(frags[0]));
+    ok(rc==MMSYSERR_NOERROR, "waveOutUnprepareHeader(%d): rc=%s\n",device,wave_out_error(rc));
+    ok(frags[0].dwFlags==0, "dwFlags(%d)=%x\n",device,frags[0].dwFlags);
 
     rc=waveOutClose(wout);
     ok(rc==MMSYSERR_NOERROR,"waveOutClose(%s): rc=%s\n",dev_name(device),
        wave_out_error(rc));
-    WaitForSingleObject(hevent,10000);
+    if (rc==WAVERR_STILLPLAYING) {
+        /* waveOutReset ought to return all buffers s.t. waveOutClose succeeds */
+        rc=waveOutReset(wout);
+        ok(rc==MMSYSERR_NOERROR,"waveOutReset(%s): rc=%s\n",dev_name(device),
+           wave_out_error(rc));
+
+        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));
+        }
+        rc=waveOutClose(wout);
+        ok(rc==MMSYSERR_NOERROR,"waveOutClose(%s): rc=%s\n",dev_name(device),
+           wave_out_error(rc));
+    }
+    rc=WaitForSingleObject(hevent,1500);
+    ok(rc==WAIT_OBJECT_0, "missing WOM_CLOSE notification\n");
+
+    wout = (HWAVEOUT)0xdeadf00d;
+    rc=waveOutOpen(&wout,device,pwfx,callback,callback_instance,flags|WAVE_FORMAT_QUERY);
+    ok(rc==MMSYSERR_NOERROR, "WAVE_FORMAT_QUERY(%s): rc=%s\n",dev_name(device),
+       wave_out_error(rc));
+    ok(wout==(HWAVEOUT)0xdeadf00d, "WAVE_FORMAT_QUERY handle %p\n", wout);
+
+    rc=WaitForSingleObject(hevent,20);
+    ok(rc==WAIT_TIMEOUT, "Notification from %s rc=%x\n",
+       wave_open_flags(flags|WAVE_FORMAT_QUERY),rc);
+
+    HeapFree(GetProcessHeap(), 0, buffer);
 EXIT:
     if ((flags & CALLBACK_TYPEMASK) == CALLBACK_THREAD) {
-        PostThreadMessage(thread_id, WM_APP, 0, 0);
+        PostThreadMessageW(thread_id, WM_APP, 0, 0);
         WaitForSingleObject(hevent,10000);
     }
     CloseHandle(hevent);
@@ -828,7 +879,7 @@ static void wave_out_test_device(UINT_PTR device)
 {
     WAVEOUTCAPSA capsA;
     WAVEOUTCAPSW capsW;
-    WAVEFORMATEX format, oformat;
+    WAVEFORMATEX format;
     WAVEFORMATEXTENSIBLE wfex;
     IMAADPCMWAVEFORMAT wfa;
     HWAVEOUT wout;
@@ -894,11 +945,16 @@ static void wave_out_test_device(UINT_PTR device)
        "waveOutGetDevCapsW(%s): unexpected return value %s\n",
        dev_name(device),wave_out_error(rc));
 
+    rc=waveOutMessage((HWAVEOUT)device, DRV_QUERYMAPPABLE, 0, 0);
+    ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NOTSUPPORTED,
+            "DRV_QUERYMAPPABLE(%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,
+    ok(rc==MMSYSERR_NOERROR || broken(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) {
@@ -967,20 +1023,20 @@ static void wave_out_test_device(UINT_PTR device)
         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,
+        wave_out_test_deviceOut(device,0.6,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,
+        wave_out_test_deviceOut(device,0.6,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,
+        wave_out_test_deviceOut(device,0.6,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,
+        wave_out_test_deviceOut(device,0.6,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,
+        wave_out_test_deviceOut(device,0.6,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,
+        wave_out_test_deviceOut(device,0.6,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,
+        wave_out_test_deviceOut(device,0.8,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);
@@ -1111,52 +1167,6 @@ static void wave_out_test_device(UINT_PTR device)
         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;
@@ -1177,10 +1187,23 @@ static void wave_out_test_device(UINT_PTR device)
                                 &capsA,winetest_interactive,TRUE,FALSE);
         wave_out_test_deviceOut(device,1.0,5,1,&format,0,CALLBACK_EVENT,
                                 &capsA,winetest_interactive,TRUE,FALSE);
-    } else
+    } else {
+        MMRESULT query_rc;
+
         trace("waveOutOpen(%s): WAVE_FORMAT_MULAW not supported\n",
               dev_name(device));
 
+        query_rc = waveOutOpen(NULL, device, &format, 0, 0, CALLBACK_NULL | WAVE_FORMAT_QUERY);
+        ok(query_rc==MMSYSERR_NOERROR || query_rc==WAVERR_BADFORMAT || query_rc==MMSYSERR_INVALPARAM,
+           "waveOutOpen(%s): returned %s\n",dev_name(device),wave_out_error(rc));
+
+        rc = waveOutOpen(&wout, device, &format, 0, 0, CALLBACK_NULL);
+        ok(rc == query_rc,
+           "waveOutOpen(%s): returned different from query: %s\n",dev_name(device),wave_out_error(rc));
+        if(rc == MMSYSERR_NOERROR)
+            waveOutClose(wout);
+    }
+
     wfa.wfx.wFormatTag=WAVE_FORMAT_IMA_ADPCM;
     wfa.wfx.nChannels=1;
     wfa.wfx.nSamplesPerSec=11025;
@@ -1402,11 +1425,22 @@ static void wave_out_tests(void)
     WAVEFORMATEX format;
     HWAVEOUT wout;
     MMRESULT rc;
+    DWORD preferred, status;
     UINT ndev,d;
 
     ndev=waveOutGetNumDevs();
     trace("found %d WaveOut devices\n",ndev);
 
+    rc = waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
+            (DWORD_PTR)&preferred, (DWORD_PTR)&status);
+    ok((ndev == 0 && (rc == MMSYSERR_NODRIVER || rc == MMSYSERR_BADDEVICEID)) ||
+            rc == MMSYSERR_NOTSUPPORTED ||
+            rc == MMSYSERR_NOERROR, "waveOutMessage(DRVM_MAPPER_PREFERRED_GET) failed: %u\n", rc);
+
+    if(rc != MMSYSERR_NOTSUPPORTED)
+        ok((ndev == 0 && (preferred == -1 || broken(preferred != -1))) ||
+                preferred < ndev, "Got invalid preferred device: 0x%x\n", preferred);
+
     rc=waveOutGetDevCapsA(ndev+1,&capsA,sizeof(capsA));
     ok(rc==MMSYSERR_BADDEVICEID,
        "waveOutGetDevCapsA(%s): MMSYSERR_BADDEVICEID expected, got %s\n",
@@ -1451,15 +1485,128 @@ static void wave_out_tests(void)
        "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(winetest_interactive)
+        for (d=0;d<ndev;d++)
+            wave_out_test_device(d);
 
     if (ndev>0)
         wave_out_test_device(WAVE_MAPPER);
 }
 
+static void test_sndPlaySound(void)
+{
+    BOOL br;
+
+    static const WCHAR not_existW[] = {'C',':','\\','n','o','t','_','e','x','i','s','t','.','w','a','v',0};
+    static const WCHAR SystemAsteriskW[] = {'S','y','s','t','e','m','A','s','t','e','r','i','s','k',0};
+
+    br = sndPlaySoundA((LPCSTR)SND_ALIAS_SYSTEMASTERISK, SND_ALIAS_ID|SND_SYNC);
+    ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br);
+
+    br = sndPlaySoundW((LPCWSTR)SND_ALIAS_SYSTEMASTERISK, SND_ALIAS_ID|SND_SYNC);
+    ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br);
+
+    br = sndPlaySoundA((LPCSTR)sndAlias('X','Y'), SND_ALIAS_ID|SND_SYNC);
+    ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br);
+
+    br = sndPlaySoundW((LPCWSTR)sndAlias('X','Y'), SND_ALIAS_ID|SND_SYNC);
+    ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br);
+
+    br = sndPlaySoundA("SystemAsterisk", SND_ALIAS|SND_SYNC);
+    ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br);
+
+    br = sndPlaySoundW(SystemAsteriskW, SND_ALIAS|SND_SYNC);
+    ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br);
+
+    br = sndPlaySoundA("C:\not_exist.wav", SND_FILENAME|SND_SYNC);
+    ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br);
+
+    br = sndPlaySoundW(not_existW, SND_FILENAME|SND_SYNC);
+    ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br);
+}
+
+static void test_fragmentsize(void)
+{
+    MMRESULT rc;
+    WAVEHDR hdr[2];
+    HWAVEOUT wout;
+    WAVEFORMATEX fmt;
+    MMTIME mmtime;
+    DWORD wait;
+    HANDLE hevent;
+
+    if(waveOutGetNumDevs() == 0)
+        return;
+
+    fmt.wFormatTag = WAVE_FORMAT_PCM;
+    fmt.nChannels = 2;
+    fmt.nSamplesPerSec = 44100;
+    fmt.wBitsPerSample = 16;
+    fmt.nBlockAlign = fmt.nChannels * fmt.wBitsPerSample / 8;
+    fmt.nAvgBytesPerSec = fmt.nBlockAlign * fmt.nSamplesPerSec;
+    fmt.cbSize = sizeof(WAVEFORMATEX);
+
+    hevent = CreateEventW(NULL, FALSE, FALSE, NULL);
+    g_tid = GetCurrentThreadId();
+
+    rc = waveOutOpen(&wout, WAVE_MAPPER, &fmt, (DWORD_PTR)callback_func,
+            (DWORD_PTR)hevent, CALLBACK_FUNCTION);
+    ok(rc == MMSYSERR_NOERROR || rc == WAVERR_BADFORMAT ||
+           rc == MMSYSERR_INVALFLAG || rc == MMSYSERR_INVALPARAM,
+           "waveOutOpen(%s) failed: %s\n", dev_name(WAVE_MAPPER), wave_out_error(rc));
+    if(rc != MMSYSERR_NOERROR){
+        CloseHandle(hevent);
+        return;
+    }
+
+    wait = WaitForSingleObject(hevent, 1000);
+    ok(wait == WAIT_OBJECT_0, "wave open callback missed\n");
+
+    memset(hdr, 0, sizeof(hdr));
+    hdr[0].dwBufferLength = (fmt.nSamplesPerSec * fmt.nBlockAlign / 4) + 1;
+    hdr[1].dwBufferLength = hdr[0].dwBufferLength - 2;
+    hdr[1].lpData = hdr[0].lpData =
+        HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, hdr[0].dwBufferLength);
+
+    rc = waveOutPrepareHeader(wout, &hdr[0], sizeof(hdr[0]));
+    ok(rc == MMSYSERR_NOERROR, "waveOutPrepareHeader failed: %s\n", wave_out_error(rc));
+
+    rc = waveOutPrepareHeader(wout, &hdr[1], sizeof(hdr[1]));
+    ok(rc == MMSYSERR_NOERROR, "waveOutPrepareHeader failed: %s\n", wave_out_error(rc));
+
+    trace("writing %u bytes then %u bytes\n", hdr[0].dwBufferLength, hdr[1].dwBufferLength);
+    rc = waveOutWrite(wout, &hdr[0], sizeof(hdr[0]));
+    ok(rc == MMSYSERR_NOERROR, "waveOutWrite failed: %s\n", wave_out_error(rc));
+
+    rc = waveOutWrite(wout, &hdr[1], sizeof(hdr[1]));
+    ok(rc == MMSYSERR_NOERROR, "waveOutWrite failed: %s\n", wave_out_error(rc));
+
+    wait = WaitForSingleObject(hevent, 1000);
+    ok(wait == WAIT_OBJECT_0, "header 1 callback missed\n");
+
+    wait = WaitForSingleObject(hevent, 1000);
+    ok(wait == WAIT_OBJECT_0, "header 2 callback missed\n");
+
+    memset(&mmtime, 0, sizeof(mmtime));
+    mmtime.wType = TIME_BYTES;
+
+    rc = waveOutGetPosition(wout, &mmtime, sizeof(mmtime));
+    ok(rc == MMSYSERR_NOERROR, "waveOutGetPosition failed: %s\n", wave_out_error(rc));
+
+    /* windows behavior is inconsistent */
+    ok(mmtime.u.cb == 88200 ||
+            mmtime.u.cb == 88196, "after position: %u\n", mmtime.u.cb);
+
+    rc = waveOutClose(wout);
+    ok(rc == MMSYSERR_NOERROR, "waveOutClose failed: %s\n", wave_out_error(rc));
+
+    CloseHandle(hevent);
+}
+
 START_TEST(wave)
 {
     test_multiple_waveopens();
     wave_out_tests();
+    test_sndPlaySound();
+    test_fragmentsize();
 }