winmm: More MIDI boundary tests & fixes.
diff --git a/dlls/winmm/tests/midi.c b/dlls/winmm/tests/midi.c
index 4ceec33..891bf70 100644
--- a/dlls/winmm/tests/midi.c
+++ b/dlls/winmm/tests/midi.c
@@ -207,13 +207,20 @@
     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);
+        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)
@@ -236,6 +243,9 @@
     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
@@ -294,6 +304,10 @@
     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));
 
@@ -324,19 +338,25 @@
     }
 }
 
+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, /* dwDeltaTime and dwStreamID */
+    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 BYTE strmNops[] = { /* Test callback + dwOffset */
-    0, 0, 0, 0,  0, 0, 0, 0,
-    0, 0, 0, MEVT_NOP | 0x40, /* with MEVT_F_CALLBACK */
-    0, 0, 0, 0,  0, 0, 0, 0,
-    0, 0, 0, MEVT_NOP | 0x40, /* with MEVT_F_CALLBACK */
+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)
@@ -357,8 +377,6 @@
         MIDIPROPTEMPO tempo;
         MIDIPROPTIMEDIV tdiv;
     } midiprop;
-    BYTE * const evt1 = &strmNops[1*offsetof(MIDIEVENT,dwParms)-1];
-    BYTE * const evt2 = &strmNops[2*offsetof(MIDIEVENT,dwParms)-1];
 
     if (hwnd)
         rc = midiStreamOpen(&hm, &udev, 1, (DWORD_PTR)hwnd, (DWORD_PTR)MYCBINST, CALLBACK_WINDOW);
@@ -382,7 +400,7 @@
     mhdr.dwFlags = 0;
     mhdr.dwUser   = 0x56FA552C;
     mhdr.dwOffset = 1234567890;
-    mhdr.dwBufferLength = sizeof(strmEvents) * sizeof(strmEvents[0]);
+    mhdr.dwBufferLength = sizeof(strmEvents);
     mhdr.dwBytesRecorded = mhdr.dwBufferLength;
     mhdr.lpData = (LPSTR)&strmEvents[0];
     if (mhdr.lpData) {
@@ -410,7 +428,7 @@
 
     /* MSDN asks to use midiStreamRestart prior to midiStreamOut()
      * because the starting state is 'pause', but some apps seem to
-     * work with the inverse order.
+     * work with the inverse order: queue everything, then play.
      */
 
         rc = midiStreamRestart(hm);
@@ -452,10 +470,15 @@
 
         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; /* just in case */
+        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));
@@ -466,16 +489,17 @@
         ok(!rc, "midiStreamRestart #3 rc=%s\n", mmsys_error(rc));
     }
     ok(mhdr.dwUser==0x56FA552C, "MIDIHDR.dwUser changed to %lx\n", mhdr.dwUser);
-    trace("dwStreamID set to %x\n", ((LPMIDIEVENT)&strmEvents[0])->dwStreamID);
+    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) * sizeof(strmNops[0]);
+    mhdr.dwBufferLength = sizeof(strmNops);
+    trace("buffer: %u\n", mhdr.dwBufferLength);
     mhdr.dwBytesRecorded = 0;
     mhdr.lpData = (LPSTR)&strmNops[0];
-    *evt1 |= 0x40; /* MEVT_CALLBACK flag */
-    *evt2 |= 0x40;
+    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));
@@ -494,7 +518,7 @@
      * dwOffset slot does not exist in the small size MIDIHDR. */
 
     mhdr.dwOffset = 1234123123;
-    mhdr.dwBytesRecorded = offsetof(MIDIEVENT,dwParms);
+    mhdr.dwBytesRecorded = 1*sizeof(MIDISHORTEVENT);
 
     rc = playStream(hm, &mhdr);
     ok(!rc, "midiStreamOut 1 event out of 2 rc=%s\n", mmsys_error(rc));
@@ -503,10 +527,9 @@
     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);
-    trace("MIDIHDR.dwOffset left at %u\n", mhdr.dwOffset);
 
     mhdr.dwOffset = 1234123123;
-    mhdr.dwBytesRecorded = 2*offsetof(MIDIEVENT,dwParms);
+    mhdr.dwBytesRecorded = 2*sizeof(MIDISHORTEVENT);
 
     rc = playStream(hm, &mhdr);
     ok(!rc, "midiStreamOut 1 event out of 2 rc=%s\n", mmsys_error(rc));
@@ -515,11 +538,11 @@
     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(3*sizeof(DWORD)==mhdr.dwOffset, "MIDIHDR.dwOffset 2/2 changed to %u\n", mhdr.dwOffset);
-    trace("MIDIHDR.dwOffset left at %u\n", mhdr.dwOffset);
+    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);
 
-    *evt1 &= ~0x40; /* MEVT_CALLBACK flag */
-    *evt2 &= ~0x40;
+    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));
@@ -527,11 +550,11 @@
     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(3*sizeof(DWORD)==mhdr.dwOffset), "MIDIHDR.dwOffset nocb changed to %u\n", mhdr.dwOffset);
+    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);
-    todo_wine ok(rc==MMSYSERR_INVALPARAM,"midiStreamOut dwBytesRecorded/MIDIEVENT rc=%s\n", mmsys_error(rc));
+    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);
     }
@@ -547,6 +570,8 @@
 
     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);
@@ -567,6 +592,21 @@
     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 void test_midi_outfns(HWND hwnd)
@@ -620,7 +660,7 @@
 
 START_TEST(midi)
 {
-    HWND hwnd;
+    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);
diff --git a/dlls/winmm/winmm.c b/dlls/winmm/winmm.c
index 1c6a13e..029a1c9 100644
--- a/dlls/winmm/winmm.c
+++ b/dlls/winmm/winmm.c
@@ -911,6 +911,13 @@
 
     if (lphMidiOut != NULL) *lphMidiOut = 0;
 
+    switch (dwFlags & CALLBACK_TYPEMASK) {
+    case CALLBACK_WINDOW:
+        if (dwCallback && !IsWindow((HWND)dwCallback))
+            return MMSYSERR_INVALPARAM;
+        break;
+    }
+
     lpwm = MIDI_OutAlloc(&hMidiOut, &dwCallback, &dwInstance, &dwFlags, 0, NULL);
 
     if (lpwm == NULL)
@@ -1754,6 +1761,13 @@
     if (cMidi != 1 || lphMidiStrm == NULL || lpuDeviceID == NULL)
 	return MMSYSERR_INVALPARAM;
 
+    switch (fdwOpen & CALLBACK_TYPEMASK) {
+    case CALLBACK_WINDOW:
+        if (dwCallback && !IsWindow((HWND)dwCallback))
+            return MMSYSERR_INVALPARAM;
+        break;
+    }
+
     lpMidiStrm = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_MIDIStream));
     if (!lpMidiStrm)
 	return MMSYSERR_NOMEM;
@@ -1816,7 +1830,8 @@
     TRACE("(%p, %p, %u)!\n", hMidiStrm, lpMidiHdr, cbMidiHdr);
 
     if (cbMidiHdr < offsetof(MIDIHDR,dwOffset) || !lpMidiHdr || !lpMidiHdr->lpData
-	|| lpMidiHdr->dwBufferLength < lpMidiHdr->dwBytesRecorded)
+	|| lpMidiHdr->dwBufferLength < lpMidiHdr->dwBytesRecorded
+	|| lpMidiHdr->dwBytesRecorded % 4 /* player expects DWORD padding */)
 	return MMSYSERR_INVALPARAM;
     /* FIXME: Native additionally checks if the MIDIEVENTs in lpData
      * exactly fit dwBytesRecorded. */