/* -*- tab-width: 8; c-basic-offset: 4 -*- */ /* * WINMM functions * * Copyright 1993 Martin Ayotte * 1998-2002 Eric Pouech * * 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 */ /* * Eric POUECH : * 98/9 added Win32 MCI support * 99/4 added midiStream support * 99/9 added support for loadable low level drivers */ /* TODO * + it seems that some programs check what's installed in * registry against the value returned by drivers. Wine is * currently broken regarding this point. * + check thread-safeness for MMSYSTEM and WINMM entry points * + unicode entry points are badly supported (would require * moving 32 bit drivers as Unicode as they are supposed to be) * + allow joystick and timer external calls as we do for wave, * midi, mixer and aux */ #include #include #include #define NONAMELESSUNION #include "windef.h" #include "winbase.h" #include "mmsystem.h" #include "winuser.h" #include "winnls.h" #include "winternl.h" #include "winemm.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(winmm); /* ======================================================================== * G L O B A L S E T T I N G S * ========================================================================*/ HINSTANCE hWinMM32Instance; HANDLE psLastEvent; static CRITICAL_SECTION_DEBUG critsect_debug = { 0, 0, &WINMM_cs, { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, 0, 0, { (DWORD_PTR)(__FILE__ ": WINMM_cs") } }; CRITICAL_SECTION WINMM_cs = { &critsect_debug, -1, 0, 0, 0, 0 }; /************************************************************************** * WINMM_CreateIData [internal] */ static BOOL WINMM_CreateIData(HINSTANCE hInstDLL) { hWinMM32Instance = hInstDLL; psLastEvent = CreateEventW(NULL, TRUE, FALSE, NULL); return psLastEvent != NULL; } /****************************************************************** * WINMM_ErrorToString */ const char* WINMM_ErrorToString(MMRESULT error) { #define ERR_TO_STR(dev) case dev: return #dev switch (error) { ERR_TO_STR(MMSYSERR_NOERROR); ERR_TO_STR(MMSYSERR_ERROR); ERR_TO_STR(MMSYSERR_BADDEVICEID); ERR_TO_STR(MMSYSERR_NOTENABLED); ERR_TO_STR(MMSYSERR_ALLOCATED); ERR_TO_STR(MMSYSERR_INVALHANDLE); ERR_TO_STR(MMSYSERR_NODRIVER); ERR_TO_STR(MMSYSERR_NOMEM); ERR_TO_STR(MMSYSERR_NOTSUPPORTED); ERR_TO_STR(MMSYSERR_BADERRNUM); ERR_TO_STR(MMSYSERR_INVALFLAG); ERR_TO_STR(MMSYSERR_INVALPARAM); ERR_TO_STR(MMSYSERR_HANDLEBUSY); ERR_TO_STR(MMSYSERR_INVALIDALIAS); ERR_TO_STR(MMSYSERR_BADDB); ERR_TO_STR(MMSYSERR_KEYNOTFOUND); ERR_TO_STR(MMSYSERR_READERROR); ERR_TO_STR(MMSYSERR_WRITEERROR); ERR_TO_STR(MMSYSERR_DELETEERROR); ERR_TO_STR(MMSYSERR_VALNOTFOUND); ERR_TO_STR(MMSYSERR_NODRIVERCB); ERR_TO_STR(WAVERR_BADFORMAT); ERR_TO_STR(WAVERR_STILLPLAYING); ERR_TO_STR(WAVERR_UNPREPARED); ERR_TO_STR(WAVERR_SYNC); ERR_TO_STR(MIDIERR_INVALIDSETUP); ERR_TO_STR(MIDIERR_NODEVICE); ERR_TO_STR(MIDIERR_STILLPLAYING); ERR_TO_STR(MIDIERR_UNPREPARED); } #undef ERR_TO_STR return wine_dbg_sprintf("Unknown(0x%08x)", error); } /************************************************************************** * DllMain (WINMM.init) * * WINMM DLL entry point * */ BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID fImpLoad) { TRACE("%p 0x%x %p\n", hInstDLL, fdwReason, fImpLoad); switch (fdwReason) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hInstDLL); if (!WINMM_CreateIData(hInstDLL)) return FALSE; break; case DLL_PROCESS_DETACH: if(fImpLoad) break; MCI_SendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_WAIT, 0L); MMDRV_Exit(); DRIVER_UnloadAll(); WINMM_DeleteWaveform(); TIME_MMTimeStop(); CloseHandle(psLastEvent); DeleteCriticalSection(&WINMM_cs); break; } return TRUE; } /************************************************************************** * WINMM_CheckCallback [internal] */ MMRESULT WINMM_CheckCallback(DWORD_PTR dwCallback, DWORD fdwOpen, BOOL mixer) { switch (fdwOpen & CALLBACK_TYPEMASK) { case CALLBACK_NULL: /* dwCallback need not be NULL */ break; case CALLBACK_WINDOW: if (dwCallback && !IsWindow((HWND)dwCallback)) return MMSYSERR_INVALPARAM; break; case CALLBACK_FUNCTION: /* a NULL cb is acceptable since w2k, MMSYSERR_INVALPARAM earlier */ if (mixer) return MMSYSERR_INVALFLAG; /* since w2k, MMSYSERR_NOTSUPPORTED earlier */ break; case CALLBACK_THREAD: case CALLBACK_EVENT: if (mixer) /* FIXME: mixer supports THREAD+EVENT since w2k */ return MMSYSERR_NOTSUPPORTED; /* w9X */ break; default: WARN("Unknown callback type %d\n", HIWORD(fdwOpen)); } return MMSYSERR_NOERROR; } /************************************************************************** * auxGetNumDevs [WINMM.@] */ UINT WINAPI auxGetNumDevs(void) { return MMDRV_GetNum(MMDRV_AUX); } /************************************************************************** * auxGetDevCapsW [WINMM.@] */ UINT WINAPI auxGetDevCapsW(UINT_PTR uDeviceID, LPAUXCAPSW lpCaps, UINT uSize) { LPWINE_MLD wmld; TRACE("(%04lX, %p, %d) !\n", uDeviceID, lpCaps, uSize); if (lpCaps == NULL) return MMSYSERR_INVALPARAM; if ((wmld = MMDRV_Get((HANDLE)uDeviceID, MMDRV_AUX, TRUE)) == NULL) return MMSYSERR_BADDEVICEID; return MMDRV_Message(wmld, AUXDM_GETDEVCAPS, (DWORD_PTR)lpCaps, uSize); } /************************************************************************** * auxGetDevCapsA [WINMM.@] */ UINT WINAPI auxGetDevCapsA(UINT_PTR uDeviceID, LPAUXCAPSA lpCaps, UINT uSize) { AUXCAPSW acW; UINT ret; if (lpCaps == NULL) return MMSYSERR_INVALPARAM; ret = auxGetDevCapsW(uDeviceID, &acW, sizeof(acW)); if (ret == MMSYSERR_NOERROR) { AUXCAPSA acA; acA.wMid = acW.wMid; acA.wPid = acW.wPid; acA.vDriverVersion = acW.vDriverVersion; WideCharToMultiByte( CP_ACP, 0, acW.szPname, -1, acA.szPname, sizeof(acA.szPname), NULL, NULL ); acA.wTechnology = acW.wTechnology; acA.wReserved1 = acW.wReserved1; acA.dwSupport = acW.dwSupport; memcpy(lpCaps, &acA, min(uSize, sizeof(acA))); } return ret; } /************************************************************************** * auxGetVolume [WINMM.@] */ UINT WINAPI auxGetVolume(UINT uDeviceID, DWORD* lpdwVolume) { LPWINE_MLD wmld; TRACE("(%04X, %p) !\n", uDeviceID, lpdwVolume); if ((wmld = MMDRV_Get((HANDLE)(DWORD_PTR)uDeviceID, MMDRV_AUX, TRUE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, AUXDM_GETVOLUME, (DWORD_PTR)lpdwVolume, 0L); } /************************************************************************** * auxSetVolume [WINMM.@] */ UINT WINAPI auxSetVolume(UINT uDeviceID, DWORD dwVolume) { LPWINE_MLD wmld; TRACE("(%04X, %u) !\n", uDeviceID, dwVolume); if ((wmld = MMDRV_Get((HANDLE)(DWORD_PTR)uDeviceID, MMDRV_AUX, TRUE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, AUXDM_SETVOLUME, dwVolume, 0L); } /************************************************************************** * auxOutMessage [WINMM.@] */ UINT WINAPI auxOutMessage(UINT uDeviceID, UINT uMessage, DWORD_PTR dw1, DWORD_PTR dw2) { LPWINE_MLD wmld; if ((wmld = MMDRV_Get((HANDLE)(DWORD_PTR)uDeviceID, MMDRV_AUX, TRUE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, uMessage, dw1, dw2); } /************************************************************************** * midiOutGetNumDevs [WINMM.@] */ UINT WINAPI midiOutGetNumDevs(void) { return MMDRV_GetNum(MMDRV_MIDIOUT); } /************************************************************************** * midiOutGetDevCapsW [WINMM.@] */ UINT WINAPI midiOutGetDevCapsW(UINT_PTR uDeviceID, LPMIDIOUTCAPSW lpCaps, UINT uSize) { LPWINE_MLD wmld; TRACE("(%lu, %p, %u);\n", uDeviceID, lpCaps, uSize); if (lpCaps == NULL) return MMSYSERR_INVALPARAM; if ((wmld = MMDRV_Get((HANDLE)uDeviceID, MMDRV_MIDIOUT, TRUE)) == NULL) return MMSYSERR_BADDEVICEID; return MMDRV_Message(wmld, MODM_GETDEVCAPS, (DWORD_PTR)lpCaps, uSize); } /************************************************************************** * midiOutGetDevCapsA [WINMM.@] */ UINT WINAPI midiOutGetDevCapsA(UINT_PTR uDeviceID, LPMIDIOUTCAPSA lpCaps, UINT uSize) { MIDIOUTCAPSW mocW; UINT ret; if (lpCaps == NULL) return MMSYSERR_INVALPARAM; ret = midiOutGetDevCapsW(uDeviceID, &mocW, sizeof(mocW)); if (ret == MMSYSERR_NOERROR) { MIDIOUTCAPSA mocA; mocA.wMid = mocW.wMid; mocA.wPid = mocW.wPid; mocA.vDriverVersion = mocW.vDriverVersion; WideCharToMultiByte( CP_ACP, 0, mocW.szPname, -1, mocA.szPname, sizeof(mocA.szPname), NULL, NULL ); mocA.wTechnology = mocW.wTechnology; mocA.wVoices = mocW.wVoices; mocA.wNotes = mocW.wNotes; mocA.wChannelMask = mocW.wChannelMask; mocA.dwSupport = mocW.dwSupport; memcpy(lpCaps, &mocA, min(uSize, sizeof(mocA))); } return ret; } /************************************************************************** * midiOutGetErrorTextA [WINMM.@] * midiInGetErrorTextA [WINMM.@] */ UINT WINAPI midiOutGetErrorTextA(UINT uError, LPSTR lpText, UINT uSize) { UINT ret; if (lpText == NULL) ret = MMSYSERR_INVALPARAM; else if (uSize == 0) ret = MMSYSERR_NOERROR; else { LPWSTR xstr = HeapAlloc(GetProcessHeap(), 0, uSize * sizeof(WCHAR)); if (!xstr) ret = MMSYSERR_NOMEM; else { ret = midiOutGetErrorTextW(uError, xstr, uSize); if (ret == MMSYSERR_NOERROR) WideCharToMultiByte(CP_ACP, 0, xstr, -1, lpText, uSize, NULL, NULL); HeapFree(GetProcessHeap(), 0, xstr); } } return ret; } /************************************************************************** * midiOutGetErrorTextW [WINMM.@] * midiInGetErrorTextW [WINMM.@] */ UINT WINAPI midiOutGetErrorTextW(UINT uError, LPWSTR lpText, UINT uSize) { UINT ret = MMSYSERR_BADERRNUM; if (lpText == NULL) ret = MMSYSERR_INVALPARAM; else if (uSize == 0) ret = MMSYSERR_NOERROR; else if ( /* test has been removed because MMSYSERR_BASE is 0, and gcc did emit * a warning for the test was always true */ (/*uError >= MMSYSERR_BASE && */ uError <= MMSYSERR_LASTERROR) || (uError >= MIDIERR_BASE && uError <= MIDIERR_LASTERROR)) { if (LoadStringW(hWinMM32Instance, uError, lpText, uSize) > 0) { ret = MMSYSERR_NOERROR; } } return ret; } /************************************************************************** * MIDI_OutAlloc [internal] */ static LPWINE_MIDI MIDI_OutAlloc(HMIDIOUT* lphMidiOut, DWORD_PTR* lpdwCallback, DWORD_PTR* lpdwInstance, LPDWORD lpdwFlags, DWORD cIDs, MIDIOPENSTRMID* lpIDs) { HANDLE hMidiOut; LPWINE_MIDI lpwm; UINT size; size = sizeof(WINE_MIDI) + (cIDs ? (cIDs-1) : 0) * sizeof(MIDIOPENSTRMID); lpwm = (LPWINE_MIDI)MMDRV_Alloc(size, MMDRV_MIDIOUT, &hMidiOut, lpdwFlags, lpdwCallback, lpdwInstance); *lphMidiOut = hMidiOut; if (lpwm) { lpwm->mod.hMidi = hMidiOut; lpwm->mod.dwCallback = *lpdwCallback; lpwm->mod.dwInstance = *lpdwInstance; lpwm->mod.dnDevNode = 0; lpwm->mod.cIds = cIDs; if (cIDs) memcpy(&(lpwm->mod.rgIds), lpIDs, cIDs * sizeof(MIDIOPENSTRMID)); } return lpwm; } /************************************************************************** * midiOutOpen [WINMM.@] */ MMRESULT WINAPI midiOutOpen(LPHMIDIOUT lphMidiOut, UINT uDeviceID, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD dwFlags) { HMIDIOUT hMidiOut; LPWINE_MIDI lpwm; MMRESULT dwRet; TRACE("(%p, %d, %08lX, %08lX, %08X);\n", lphMidiOut, uDeviceID, dwCallback, dwInstance, dwFlags); if (lphMidiOut != NULL) *lphMidiOut = 0; dwRet = WINMM_CheckCallback(dwCallback, dwFlags, FALSE); if (dwRet != MMSYSERR_NOERROR) return dwRet; lpwm = MIDI_OutAlloc(&hMidiOut, &dwCallback, &dwInstance, &dwFlags, 0, NULL); if (lpwm == NULL) return MMSYSERR_NOMEM; lpwm->mld.uDeviceID = uDeviceID; dwRet = MMDRV_Open((LPWINE_MLD)lpwm, MODM_OPEN, (DWORD_PTR)&lpwm->mod, dwFlags); if (dwRet != MMSYSERR_NOERROR) { MMDRV_Free(hMidiOut, (LPWINE_MLD)lpwm); hMidiOut = 0; } if (lphMidiOut) *lphMidiOut = hMidiOut; TRACE("=> %d hMidi=%p\n", dwRet, hMidiOut); return dwRet; } /************************************************************************** * midiOutClose [WINMM.@] */ UINT WINAPI midiOutClose(HMIDIOUT hMidiOut) { LPWINE_MLD wmld; DWORD dwRet; TRACE("(%p)\n", hMidiOut); if ((wmld = MMDRV_Get(hMidiOut, MMDRV_MIDIOUT, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; dwRet = MMDRV_Close(wmld, MODM_CLOSE); MMDRV_Free(hMidiOut, wmld); return dwRet; } /************************************************************************** * midiOutPrepareHeader [WINMM.@] */ UINT WINAPI midiOutPrepareHeader(HMIDIOUT hMidiOut, MIDIHDR* lpMidiOutHdr, UINT uSize) { LPWINE_MLD wmld; TRACE("(%p, %p, %d)\n", hMidiOut, lpMidiOutHdr, uSize); if ((wmld = MMDRV_Get(hMidiOut, MMDRV_MIDIOUT, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; /* FIXME: detect MIDIStream handles and enforce 64KB buffer limit on those */ return MMDRV_Message(wmld, MODM_PREPARE, (DWORD_PTR)lpMidiOutHdr, uSize); } /************************************************************************** * midiOutUnprepareHeader [WINMM.@] */ UINT WINAPI midiOutUnprepareHeader(HMIDIOUT hMidiOut, MIDIHDR* lpMidiOutHdr, UINT uSize) { LPWINE_MLD wmld; TRACE("(%p, %p, %d)\n", hMidiOut, lpMidiOutHdr, uSize); if ((wmld = MMDRV_Get(hMidiOut, MMDRV_MIDIOUT, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MODM_UNPREPARE, (DWORD_PTR)lpMidiOutHdr, uSize); } /************************************************************************** * midiOutShortMsg [WINMM.@] */ UINT WINAPI midiOutShortMsg(HMIDIOUT hMidiOut, DWORD dwMsg) { LPWINE_MLD wmld; TRACE("(%p, %08X)\n", hMidiOut, dwMsg); if ((wmld = MMDRV_Get(hMidiOut, MMDRV_MIDIOUT, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MODM_DATA, dwMsg, 0L); } /************************************************************************** * midiOutLongMsg [WINMM.@] */ UINT WINAPI midiOutLongMsg(HMIDIOUT hMidiOut, MIDIHDR* lpMidiOutHdr, UINT uSize) { LPWINE_MLD wmld; TRACE("(%p, %p, %d)\n", hMidiOut, lpMidiOutHdr, uSize); if ((wmld = MMDRV_Get(hMidiOut, MMDRV_MIDIOUT, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MODM_LONGDATA, (DWORD_PTR)lpMidiOutHdr, uSize); } /************************************************************************** * midiOutReset [WINMM.@] */ UINT WINAPI midiOutReset(HMIDIOUT hMidiOut) { LPWINE_MLD wmld; TRACE("(%p)\n", hMidiOut); if ((wmld = MMDRV_Get(hMidiOut, MMDRV_MIDIOUT, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MODM_RESET, 0L, 0L); } /************************************************************************** * midiOutGetVolume [WINMM.@] */ UINT WINAPI midiOutGetVolume(HMIDIOUT hMidiOut, DWORD* lpdwVolume) { LPWINE_MLD wmld; TRACE("(%p, %p);\n", hMidiOut, lpdwVolume); if ((wmld = MMDRV_Get(hMidiOut, MMDRV_MIDIOUT, TRUE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MODM_GETVOLUME, (DWORD_PTR)lpdwVolume, 0L); } /************************************************************************** * midiOutSetVolume [WINMM.@] */ UINT WINAPI midiOutSetVolume(HMIDIOUT hMidiOut, DWORD dwVolume) { LPWINE_MLD wmld; TRACE("(%p, %d);\n", hMidiOut, dwVolume); if ((wmld = MMDRV_Get(hMidiOut, MMDRV_MIDIOUT, TRUE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MODM_SETVOLUME, dwVolume, 0L); } /************************************************************************** * midiOutCachePatches [WINMM.@] */ UINT WINAPI midiOutCachePatches(HMIDIOUT hMidiOut, UINT uBank, WORD* lpwPatchArray, UINT uFlags) { /* not really necessary to support this */ FIXME("(%p, %u, %p, %x): Stub\n", hMidiOut, uBank, lpwPatchArray, uFlags); return MMSYSERR_NOTSUPPORTED; } /************************************************************************** * midiOutCacheDrumPatches [WINMM.@] */ UINT WINAPI midiOutCacheDrumPatches(HMIDIOUT hMidiOut, UINT uPatch, WORD* lpwKeyArray, UINT uFlags) { FIXME("(%p, %u, %p, %x): Stub\n", hMidiOut, uPatch, lpwKeyArray, uFlags); return MMSYSERR_NOTSUPPORTED; } /************************************************************************** * midiOutGetID [WINMM.@] */ UINT WINAPI midiOutGetID(HMIDIOUT hMidiOut, UINT* lpuDeviceID) { LPWINE_MLD wmld; TRACE("(%p, %p)\n", hMidiOut, lpuDeviceID); if (lpuDeviceID == NULL) return MMSYSERR_INVALPARAM; if ((wmld = MMDRV_Get(hMidiOut, MMDRV_MIDIOUT, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; *lpuDeviceID = wmld->uDeviceID; return MMSYSERR_NOERROR; } /************************************************************************** * midiOutMessage [WINMM.@] */ UINT WINAPI midiOutMessage(HMIDIOUT hMidiOut, UINT uMessage, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { LPWINE_MLD wmld; TRACE("(%p, %04X, %08lX, %08lX)\n", hMidiOut, uMessage, dwParam1, dwParam2); if ((wmld = MMDRV_Get(hMidiOut, MMDRV_MIDIOUT, FALSE)) == NULL) { /* HACK... */ if (uMessage == 0x0001) { *(LPDWORD)dwParam1 = 1; return 0; } if ((wmld = MMDRV_Get(hMidiOut, MMDRV_MIDIOUT, TRUE)) != NULL) { return MMDRV_PhysicalFeatures(wmld, uMessage, dwParam1, dwParam2); } return MMSYSERR_INVALHANDLE; } switch (uMessage) { case MODM_OPEN: case MODM_CLOSE: FIXME("can't handle OPEN or CLOSE message!\n"); return MMSYSERR_NOTSUPPORTED; } return MMDRV_Message(wmld, uMessage, dwParam1, dwParam2); } /************************************************************************** * midiInGetNumDevs [WINMM.@] */ UINT WINAPI midiInGetNumDevs(void) { return MMDRV_GetNum(MMDRV_MIDIIN); } /************************************************************************** * midiInGetDevCapsW [WINMM.@] */ UINT WINAPI midiInGetDevCapsW(UINT_PTR uDeviceID, LPMIDIINCAPSW lpCaps, UINT uSize) { LPWINE_MLD wmld; TRACE("(%ld, %p, %d);\n", uDeviceID, lpCaps, uSize); if (lpCaps == NULL) return MMSYSERR_INVALPARAM; if ((wmld = MMDRV_Get((HANDLE)uDeviceID, MMDRV_MIDIIN, TRUE)) == NULL) return MMSYSERR_BADDEVICEID; return MMDRV_Message(wmld, MIDM_GETDEVCAPS, (DWORD_PTR)lpCaps, uSize); } /************************************************************************** * midiInGetDevCapsA [WINMM.@] */ UINT WINAPI midiInGetDevCapsA(UINT_PTR uDeviceID, LPMIDIINCAPSA lpCaps, UINT uSize) { MIDIINCAPSW micW; UINT ret; if (lpCaps == NULL) return MMSYSERR_INVALPARAM; ret = midiInGetDevCapsW(uDeviceID, &micW, sizeof(micW)); if (ret == MMSYSERR_NOERROR) { MIDIINCAPSA micA; micA.wMid = micW.wMid; micA.wPid = micW.wPid; micA.vDriverVersion = micW.vDriverVersion; WideCharToMultiByte( CP_ACP, 0, micW.szPname, -1, micA.szPname, sizeof(micA.szPname), NULL, NULL ); micA.dwSupport = micW.dwSupport; memcpy(lpCaps, &micA, min(uSize, sizeof(micA))); } return ret; } /************************************************************************** * midiInOpen [WINMM.@] */ MMRESULT WINAPI midiInOpen(HMIDIIN* lphMidiIn, UINT uDeviceID, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD dwFlags) { HANDLE hMidiIn; LPWINE_MIDI lpwm; MMRESULT dwRet; TRACE("(%p, %d, %08lX, %08lX, %08X);\n", lphMidiIn, uDeviceID, dwCallback, dwInstance, dwFlags); if (lphMidiIn != NULL) *lphMidiIn = 0; dwRet = WINMM_CheckCallback(dwCallback, dwFlags, FALSE); if (dwRet != MMSYSERR_NOERROR) return dwRet; lpwm = (LPWINE_MIDI)MMDRV_Alloc(sizeof(WINE_MIDI), MMDRV_MIDIIN, &hMidiIn, &dwFlags, &dwCallback, &dwInstance); if (lpwm == NULL) return MMSYSERR_NOMEM; lpwm->mod.hMidi = hMidiIn; lpwm->mod.dwCallback = dwCallback; lpwm->mod.dwInstance = dwInstance; lpwm->mld.uDeviceID = uDeviceID; dwRet = MMDRV_Open(&lpwm->mld, MIDM_OPEN, (DWORD_PTR)&lpwm->mod, dwFlags); if (dwRet != MMSYSERR_NOERROR) { MMDRV_Free(hMidiIn, &lpwm->mld); hMidiIn = 0; } if (lphMidiIn != NULL) *lphMidiIn = hMidiIn; TRACE("=> %d hMidi=%p\n", dwRet, hMidiIn); return dwRet; } /************************************************************************** * midiInClose [WINMM.@] */ UINT WINAPI midiInClose(HMIDIIN hMidiIn) { LPWINE_MLD wmld; DWORD dwRet; TRACE("(%p)\n", hMidiIn); if ((wmld = MMDRV_Get(hMidiIn, MMDRV_MIDIIN, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; dwRet = MMDRV_Close(wmld, MIDM_CLOSE); MMDRV_Free(hMidiIn, wmld); return dwRet; } /************************************************************************** * midiInPrepareHeader [WINMM.@] */ UINT WINAPI midiInPrepareHeader(HMIDIIN hMidiIn, MIDIHDR* lpMidiInHdr, UINT uSize) { LPWINE_MLD wmld; TRACE("(%p, %p, %d)\n", hMidiIn, lpMidiInHdr, uSize); if ((wmld = MMDRV_Get(hMidiIn, MMDRV_MIDIIN, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MIDM_PREPARE, (DWORD_PTR)lpMidiInHdr, uSize); } /************************************************************************** * midiInUnprepareHeader [WINMM.@] */ UINT WINAPI midiInUnprepareHeader(HMIDIIN hMidiIn, MIDIHDR* lpMidiInHdr, UINT uSize) { LPWINE_MLD wmld; TRACE("(%p, %p, %d)\n", hMidiIn, lpMidiInHdr, uSize); if ((wmld = MMDRV_Get(hMidiIn, MMDRV_MIDIIN, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MIDM_UNPREPARE, (DWORD_PTR)lpMidiInHdr, uSize); } /************************************************************************** * midiInAddBuffer [WINMM.@] */ UINT WINAPI midiInAddBuffer(HMIDIIN hMidiIn, MIDIHDR* lpMidiInHdr, UINT uSize) { LPWINE_MLD wmld; TRACE("(%p, %p, %d)\n", hMidiIn, lpMidiInHdr, uSize); if ((wmld = MMDRV_Get(hMidiIn, MMDRV_MIDIIN, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MIDM_ADDBUFFER, (DWORD_PTR)lpMidiInHdr, uSize); } /************************************************************************** * midiInStart [WINMM.@] */ UINT WINAPI midiInStart(HMIDIIN hMidiIn) { LPWINE_MLD wmld; TRACE("(%p)\n", hMidiIn); if ((wmld = MMDRV_Get(hMidiIn, MMDRV_MIDIIN, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MIDM_START, 0L, 0L); } /************************************************************************** * midiInStop [WINMM.@] */ UINT WINAPI midiInStop(HMIDIIN hMidiIn) { LPWINE_MLD wmld; TRACE("(%p)\n", hMidiIn); if ((wmld = MMDRV_Get(hMidiIn, MMDRV_MIDIIN, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MIDM_STOP, 0L, 0L); } /************************************************************************** * midiInReset [WINMM.@] */ UINT WINAPI midiInReset(HMIDIIN hMidiIn) { LPWINE_MLD wmld; TRACE("(%p)\n", hMidiIn); if ((wmld = MMDRV_Get(hMidiIn, MMDRV_MIDIIN, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; return MMDRV_Message(wmld, MIDM_RESET, 0L, 0L); } /************************************************************************** * midiInGetID [WINMM.@] */ UINT WINAPI midiInGetID(HMIDIIN hMidiIn, UINT* lpuDeviceID) { LPWINE_MLD wmld; TRACE("(%p, %p)\n", hMidiIn, lpuDeviceID); if (lpuDeviceID == NULL) return MMSYSERR_INVALPARAM; if ((wmld = MMDRV_Get(hMidiIn, MMDRV_MIDIIN, TRUE)) == NULL) return MMSYSERR_INVALHANDLE; *lpuDeviceID = wmld->uDeviceID; return MMSYSERR_NOERROR; } /************************************************************************** * midiInMessage [WINMM.@] */ UINT WINAPI midiInMessage(HMIDIIN hMidiIn, UINT uMessage, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { LPWINE_MLD wmld; TRACE("(%p, %04X, %08lX, %08lX)\n", hMidiIn, uMessage, dwParam1, dwParam2); if ((wmld = MMDRV_Get(hMidiIn, MMDRV_MIDIIN, FALSE)) == NULL) return MMSYSERR_INVALHANDLE; switch (uMessage) { case MIDM_OPEN: case MIDM_CLOSE: FIXME("can't handle OPEN or CLOSE message!\n"); return MMSYSERR_NOTSUPPORTED; } return MMDRV_Message(wmld, uMessage, dwParam1, dwParam2); } /************************************************************************** * midiConnect [WINMM.@] */ MMRESULT WINAPI midiConnect(HMIDI hMidi, HMIDIOUT hmo, LPVOID pReserved) { FIXME("(%p, %p, %p): Stub\n", hMidi, hmo, pReserved); return MMSYSERR_ERROR; } /************************************************************************** * midiDisconnect [WINMM.@] */ MMRESULT WINAPI midiDisconnect(HMIDI hMidi, HMIDIOUT hmo, LPVOID pReserved) { FIXME("(%p, %p, %p): Stub\n", hMidi, hmo, pReserved); return MMSYSERR_ERROR; } typedef struct WINE_MIDIStream { HMIDIOUT hDevice; HANDLE hThread; DWORD dwThreadID; DWORD dwTempo; DWORD dwTimeDiv; DWORD dwPositionMS; DWORD dwPulses; DWORD dwStartTicks; WORD wFlags; HANDLE hEvent; LPMIDIHDR lpMidiHdr; } WINE_MIDIStream; #define WINE_MSM_HEADER (WM_USER+0) #define WINE_MSM_STOP (WM_USER+1) #define WINE_MSM_PAUSE (WM_USER+2) #define WINE_MSM_RESUME (WM_USER+3) /************************************************************************** * MMSYSTEM_GetMidiStream [internal] */ static BOOL MMSYSTEM_GetMidiStream(HMIDISTRM hMidiStrm, WINE_MIDIStream** lpMidiStrm, WINE_MIDI** lplpwm) { WINE_MIDI* lpwm = (LPWINE_MIDI)MMDRV_Get(hMidiStrm, MMDRV_MIDIOUT, FALSE); if (lplpwm) *lplpwm = lpwm; if (lpwm == NULL) { return FALSE; } *lpMidiStrm = (WINE_MIDIStream*)lpwm->mod.rgIds.dwStreamID; return *lpMidiStrm != NULL; } /************************************************************************** * MMSYSTEM_MidiStream_Convert [internal] */ static DWORD MMSYSTEM_MidiStream_Convert(WINE_MIDIStream* lpMidiStrm, DWORD pulse) { DWORD ret = 0; if (lpMidiStrm->dwTimeDiv == 0) { FIXME("Shouldn't happen. lpMidiStrm->dwTimeDiv = 0\n"); } else if (lpMidiStrm->dwTimeDiv > 0x8000) { /* SMPTE, unchecked FIXME? */ int nf = -(char)HIBYTE(lpMidiStrm->dwTimeDiv); /* number of frames */ int nsf = LOBYTE(lpMidiStrm->dwTimeDiv); /* number of sub-frames */ ret = (pulse * 1000) / (nf * nsf); } else { ret = (DWORD)((double)pulse * ((double)lpMidiStrm->dwTempo / 1000) / (double)lpMidiStrm->dwTimeDiv); } return ret; } /************************************************************************** * MMSYSTEM_MidiStream_MessageHandler [internal] */ static BOOL MMSYSTEM_MidiStream_MessageHandler(WINE_MIDIStream* lpMidiStrm, LPWINE_MIDI lpwm, LPMSG msg) { LPMIDIHDR lpMidiHdr; LPMIDIHDR* lpmh; LPBYTE lpData; BOOL paused = FALSE; for (;;) { switch (msg->message) { case WM_QUIT: return FALSE; case WINE_MSM_STOP: TRACE("STOP\n"); /* this is not quite what MS doc says... */ midiOutReset(lpMidiStrm->hDevice); /* empty list of already submitted buffers */ lpMidiHdr = lpMidiStrm->lpMidiHdr; lpMidiStrm->lpMidiHdr = NULL; while (lpMidiHdr) { LPMIDIHDR lphdr = lpMidiHdr; lpMidiHdr = lpMidiHdr->lpNext; lphdr->dwFlags |= MHDR_DONE; lphdr->dwFlags &= ~MHDR_INQUEUE; DriverCallback(lpwm->mod.dwCallback, lpMidiStrm->wFlags, (HDRVR)lpMidiStrm->hDevice, MM_MOM_DONE, lpwm->mod.dwInstance, (DWORD_PTR)lphdr, 0); } return TRUE; case WINE_MSM_RESUME: /* FIXME: send out cc64 0 (turn off sustain pedal) on every channel */ lpMidiStrm->dwStartTicks = GetTickCount() - lpMidiStrm->dwPositionMS; return TRUE; case WINE_MSM_PAUSE: /* FIXME: send out cc64 0 (turn off sustain pedal) on every channel */ paused = TRUE; break; /* FIXME(EPP): "I don't understand the content of the first MIDIHDR sent * by native mcimidi, it doesn't look like a correct one". * this trick allows us to throw it away... but I don't like it. * It looks like part of the file I'm trying to play and definitively looks * like raw midi content. * I'd really like to understand why native mcimidi sends it. Perhaps a bad * synchronization issue where native mcimidi is still processing raw MIDI * content before generating MIDIEVENTs ? * * 4c 04 89 3b 00 81 7c 99 3b 43 00 99 23 5e 04 89 L..;..|.;C..#^.. * 3b 00 00 89 23 00 7c 99 3b 45 00 99 28 62 04 89 ;...#.|.;E..(b.. * 3b 00 00 89 28 00 81 7c 99 3b 4e 00 99 23 5e 04 ;...(..|.;N..#^. * 89 3b 00 00 89 23 00 7c 99 3b 45 00 99 23 78 04 .;...#.|.;E..#x. * 89 3b 00 00 89 23 00 81 7c 99 3b 48 00 99 23 5e .;...#..|.;H..#^ * 04 89 3b 00 00 89 23 00 7c 99 3b 4e 00 99 28 62 ..;...#.|.;N..(b * 04 89 3b 00 00 89 28 00 81 7c 99 39 4c 00 99 23 ..;...(..|.9L..# * 5e 04 89 39 00 00 89 23 00 82 7c 99 3b 4c 00 99 ^..9...#..|.;L.. * 23 5e 04 89 3b 00 00 89 23 00 7c 99 3b 48 00 99 #^..;...#.|.;H.. * 28 62 04 89 3b 00 00 89 28 00 81 7c 99 3b 3f 04 (b..;...(..|.;?. * 89 3b 00 1c 99 23 5e 04 89 23 00 5c 99 3b 45 00 .;...#^..#.\.;E. * 99 23 78 04 89 3b 00 00 89 23 00 81 7c 99 3b 46 .#x..;...#..|.;F * 00 99 23 5e 04 89 3b 00 00 89 23 00 7c 99 3b 48 ..#^..;...#.|.;H * 00 99 28 62 04 89 3b 00 00 89 28 00 81 7c 99 3b ..(b..;...(..|.; * 46 00 99 23 5e 04 89 3b 00 00 89 23 00 7c 99 3b F..#^..;...#.|.; * 48 00 99 23 78 04 89 3b 00 00 89 23 00 81 7c 99 H..#x..;...#..|. * 3b 4c 00 99 23 5e 04 89 3b 00 00 89 23 00 7c 99 ;L..#^..;...#.|. */ case WINE_MSM_HEADER: /* sets initial tick count for first MIDIHDR */ if (!lpMidiStrm->dwStartTicks) lpMidiStrm->dwStartTicks = GetTickCount(); lpMidiHdr = (LPMIDIHDR)msg->lParam; lpData = (LPBYTE)lpMidiHdr->lpData; TRACE("Adding %s lpMidiHdr=%p [lpData=0x%p dwBytesRecorded=%u/%u dwFlags=0x%08x size=%lu]\n", (lpMidiHdr->dwFlags & MHDR_ISSTRM) ? "stream" : "regular", lpMidiHdr, lpData, lpMidiHdr->dwBytesRecorded, lpMidiHdr->dwBufferLength, lpMidiHdr->dwFlags, msg->wParam); #if 0 /* dumps content of lpMidiHdr->lpData * FIXME: there should be a debug routine somewhere that already does this */ for (dwToGo = 0; dwToGo < lpMidiHdr->dwBufferLength; dwToGo += 16) { DWORD i; BYTE ch; for (i = 0; i < min(16, lpMidiHdr->dwBufferLength - dwToGo); i++) printf("%02x ", lpData[dwToGo + i]); for (; i < 16; i++) printf(" "); for (i = 0; i < min(16, lpMidiHdr->dwBufferLength - dwToGo); i++) { ch = lpData[dwToGo + i]; printf("%c", (ch >= 0x20 && ch <= 0x7F) ? ch : '.'); } printf("\n"); } #endif if (((LPMIDIEVENT)lpData)->dwStreamID != 0 && ((LPMIDIEVENT)lpData)->dwStreamID != 0xFFFFFFFF && ((LPMIDIEVENT)lpData)->dwStreamID != (DWORD)lpMidiStrm) { FIXME("Dropping bad %s lpMidiHdr (streamID=%08x)\n", (lpMidiHdr->dwFlags & MHDR_ISSTRM) ? "stream" : "regular", ((LPMIDIEVENT)lpData)->dwStreamID); lpMidiHdr->dwFlags &= ~MHDR_INQUEUE; lpMidiHdr->dwFlags |= MHDR_DONE; DriverCallback(lpwm->mod.dwCallback, lpMidiStrm->wFlags, (HDRVR)lpMidiStrm->hDevice, MM_MOM_DONE, lpwm->mod.dwInstance, (DWORD_PTR)lpMidiHdr, 0); break; } lpMidiHdr->lpNext = 0; for (lpmh = &lpMidiStrm->lpMidiHdr; *lpmh; lpmh = &(*lpmh)->lpNext); *lpmh = lpMidiHdr; break; default: FIXME("Unknown message %d\n", msg->message); break; } if (!paused) return TRUE; GetMessageA(msg, 0, 0, 0); } } /************************************************************************** * MMSYSTEM_MidiStream_Player [internal] */ static DWORD CALLBACK MMSYSTEM_MidiStream_Player(LPVOID pmt) { WINE_MIDIStream* lpMidiStrm = pmt; WINE_MIDI* lpwm; MSG msg; DWORD dwToGo; DWORD dwCurrTC; LPMIDIHDR lpMidiHdr; DWORD dwOffset; TRACE("(%p)!\n", lpMidiStrm); if (!lpMidiStrm || (lpwm = (LPWINE_MIDI)MMDRV_Get(lpMidiStrm->hDevice, MMDRV_MIDIOUT, FALSE)) == NULL) goto the_end; /* force thread's queue creation */ PeekMessageA(&msg, 0, 0, 0, PM_NOREMOVE); lpMidiStrm->dwStartTicks = 0; lpMidiStrm->dwPulses = 0; lpMidiStrm->lpMidiHdr = 0; /* midiStreamOpen is waiting for ack */ SetEvent(lpMidiStrm->hEvent); start_header: lpMidiHdr = lpMidiStrm->lpMidiHdr; if (!lpMidiHdr) { /* for first message, block until one arrives, then process all that are available */ GetMessageA(&msg, 0, 0, 0); do { if (!MMSYSTEM_MidiStream_MessageHandler(lpMidiStrm, lpwm, &msg)) goto the_end; } while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)); goto start_header; } dwOffset = 0; while (dwOffset + offsetof(MIDIEVENT,dwParms) <= lpMidiHdr->dwBytesRecorded) { LPMIDIEVENT me = (LPMIDIEVENT)(lpMidiHdr->lpData+dwOffset); /* do we have to wait ? */ if (me->dwDeltaTime) { lpMidiStrm->dwPositionMS += MMSYSTEM_MidiStream_Convert(lpMidiStrm, me->dwDeltaTime); lpMidiStrm->dwPulses += me->dwDeltaTime; dwToGo = lpMidiStrm->dwStartTicks + lpMidiStrm->dwPositionMS; TRACE("%u/%u/%u\n", dwToGo, GetTickCount(), me->dwDeltaTime); while (dwToGo - (dwCurrTC = GetTickCount()) <= MAXLONG) { if (MsgWaitForMultipleObjects(0, NULL, FALSE, dwToGo - dwCurrTC, QS_ALLINPUT) == WAIT_OBJECT_0) { /* got a message, handle it */ while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) { if (!MMSYSTEM_MidiStream_MessageHandler(lpMidiStrm, lpwm, &msg)) goto the_end; /* is lpMidiHdr still current? */ if (lpMidiHdr != lpMidiStrm->lpMidiHdr) { goto start_header; } } } else { /* timeout, so me->dwDeltaTime is elapsed, can break the while loop */ break; } } } switch (MEVT_EVENTTYPE(me->dwEvent & ~MEVT_F_CALLBACK)) { case MEVT_COMMENT: FIXME("NIY: MEVT_COMMENT\n"); /* do nothing, skip bytes */ break; case MEVT_LONGMSG: { MIDIHDR mh; memset(&mh, 0, sizeof(mh)); mh.lpData = (LPSTR)me->dwParms; mh.dwBufferLength = MEVT_EVENTPARM(me->dwEvent); midiOutPrepareHeader(lpMidiStrm->hDevice, &mh, sizeof(mh)); midiOutLongMsg(lpMidiStrm->hDevice, &mh, sizeof(mh)); midiOutUnprepareHeader(lpMidiStrm->hDevice, &mh, sizeof(mh)); break; } case MEVT_NOP: break; case MEVT_SHORTMSG: midiOutShortMsg(lpMidiStrm->hDevice, MEVT_EVENTPARM(me->dwEvent)); break; case MEVT_TEMPO: lpMidiStrm->dwTempo = MEVT_EVENTPARM(me->dwEvent); break; case MEVT_VERSION: break; default: FIXME("Unknown MEVT (0x%02x)\n", MEVT_EVENTTYPE(me->dwEvent & ~MEVT_F_CALLBACK)); break; } if (me->dwEvent & MEVT_F_CALLBACK) { /* native fills dwOffset regardless of the cbMidiHdr size argument to midiStreamOut */ lpMidiHdr->dwOffset = dwOffset; DriverCallback(lpwm->mod.dwCallback, lpMidiStrm->wFlags, (HDRVR)lpMidiStrm->hDevice, MM_MOM_POSITIONCB, lpwm->mod.dwInstance, (LPARAM)lpMidiHdr, 0L); } dwOffset += offsetof(MIDIEVENT,dwParms); if (me->dwEvent & MEVT_F_LONG) dwOffset += (MEVT_EVENTPARM(me->dwEvent) + 3) & ~3; } /* done with this header */ lpMidiStrm->lpMidiHdr = lpMidiHdr->lpNext; lpMidiHdr->dwFlags |= MHDR_DONE; lpMidiHdr->dwFlags &= ~MHDR_INQUEUE; DriverCallback(lpwm->mod.dwCallback, lpMidiStrm->wFlags, (HDRVR)lpMidiStrm->hDevice, MM_MOM_DONE, lpwm->mod.dwInstance, (DWORD_PTR)lpMidiHdr, 0); goto start_header; the_end: TRACE("End of thread\n"); return 0; } /************************************************************************** * midiStreamClose [WINMM.@] */ MMRESULT WINAPI midiStreamClose(HMIDISTRM hMidiStrm) { WINE_MIDIStream* lpMidiStrm; MMRESULT ret = 0; TRACE("(%p)!\n", hMidiStrm); if (!MMSYSTEM_GetMidiStream(hMidiStrm, &lpMidiStrm, NULL)) return MMSYSERR_INVALHANDLE; midiStreamStop(hMidiStrm); PostThreadMessageA(lpMidiStrm->dwThreadID, WM_QUIT, 0, 0); CloseHandle(lpMidiStrm->hEvent); if (lpMidiStrm->hThread) { if (GetCurrentThreadId() != lpMidiStrm->dwThreadID) WaitForSingleObject(lpMidiStrm->hThread, INFINITE); else { FIXME("leak from call within function callback\n"); ret = MMSYSERR_HANDLEBUSY; /* yet don't signal it to app */ } CloseHandle(lpMidiStrm->hThread); } if(!ret) HeapFree(GetProcessHeap(), 0, lpMidiStrm); return midiOutClose((HMIDIOUT)hMidiStrm); } /************************************************************************** * midiStreamOpen [WINMM.@] */ MMRESULT WINAPI midiStreamOpen(HMIDISTRM* lphMidiStrm, LPUINT lpuDeviceID, DWORD cMidi, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen) { WINE_MIDIStream* lpMidiStrm; MMRESULT ret; MIDIOPENSTRMID mosm; LPWINE_MIDI lpwm; HMIDIOUT hMidiOut; TRACE("(%p, %p, %d, 0x%08lx, 0x%08lx, 0x%08x)!\n", lphMidiStrm, lpuDeviceID, cMidi, dwCallback, dwInstance, fdwOpen); if (cMidi != 1 || lphMidiStrm == NULL || lpuDeviceID == NULL) return MMSYSERR_INVALPARAM; ret = WINMM_CheckCallback(dwCallback, fdwOpen, FALSE); if (ret != MMSYSERR_NOERROR) return ret; lpMidiStrm = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_MIDIStream)); if (!lpMidiStrm) return MMSYSERR_NOMEM; lpMidiStrm->dwTempo = 500000; lpMidiStrm->dwTimeDiv = 480; /* 480 is 120 quarter notes per minute *//* FIXME ??*/ lpMidiStrm->dwPositionMS = 0; mosm.dwStreamID = (DWORD)lpMidiStrm; /* FIXME: the correct value is not allocated yet for MAPPER */ mosm.wDeviceID = *lpuDeviceID; lpwm = MIDI_OutAlloc(&hMidiOut, &dwCallback, &dwInstance, &fdwOpen, 1, &mosm); if (!lpwm) { HeapFree(GetProcessHeap(), 0, lpMidiStrm); return MMSYSERR_NOMEM; } lpMidiStrm->hDevice = hMidiOut; *lphMidiStrm = (HMIDISTRM)hMidiOut; lpwm->mld.uDeviceID = *lpuDeviceID; ret = MMDRV_Open(&lpwm->mld, MODM_OPEN, (DWORD_PTR)&lpwm->mod, fdwOpen); if (ret != MMSYSERR_NOERROR) { MMDRV_Free(hMidiOut, &lpwm->mld); HeapFree(GetProcessHeap(), 0, lpMidiStrm); return ret; } lpMidiStrm->hEvent = CreateEventW(NULL, FALSE, FALSE, NULL); lpMidiStrm->wFlags = HIWORD(fdwOpen); lpMidiStrm->hThread = CreateThread(NULL, 0, MMSYSTEM_MidiStream_Player, lpMidiStrm, 0, &(lpMidiStrm->dwThreadID)); if (!lpMidiStrm->hThread) { midiStreamClose((HMIDISTRM)hMidiOut); return MMSYSERR_NOMEM; } SetThreadPriority(lpMidiStrm->hThread, THREAD_PRIORITY_TIME_CRITICAL); /* wait for thread to have started, and for its queue to be created */ WaitForSingleObject(lpMidiStrm->hEvent, INFINITE); PostThreadMessageA(lpMidiStrm->dwThreadID, WINE_MSM_PAUSE, 0, 0); TRACE("=> (%u/%d) hMidi=%p ret=%d lpMidiStrm=%p\n", *lpuDeviceID, lpwm->mld.uDeviceID, *lphMidiStrm, ret, lpMidiStrm); return ret; } /************************************************************************** * midiStreamOut [WINMM.@] */ MMRESULT WINAPI midiStreamOut(HMIDISTRM hMidiStrm, LPMIDIHDR lpMidiHdr, UINT cbMidiHdr) { WINE_MIDIStream* lpMidiStrm; DWORD ret = MMSYSERR_NOERROR; TRACE("(%p, %p, %u)!\n", hMidiStrm, lpMidiHdr, cbMidiHdr); if (cbMidiHdr < offsetof(MIDIHDR,dwOffset) || !lpMidiHdr || !lpMidiHdr->lpData || 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. */ if (!(lpMidiHdr->dwFlags & MHDR_PREPARED)) return MIDIERR_UNPREPARED; if (lpMidiHdr->dwFlags & MHDR_INQUEUE) return MIDIERR_STILLPLAYING; if (!MMSYSTEM_GetMidiStream(hMidiStrm, &lpMidiStrm, NULL)) { ret = MMSYSERR_INVALHANDLE; } else { lpMidiHdr->dwFlags |= MHDR_ISSTRM | MHDR_INQUEUE; lpMidiHdr->dwFlags &= ~MHDR_DONE; if (!PostThreadMessageA(lpMidiStrm->dwThreadID, WINE_MSM_HEADER, cbMidiHdr, (LPARAM)lpMidiHdr)) { ERR("bad PostThreadMessageA\n"); ret = MMSYSERR_ERROR; } } return ret; } /************************************************************************** * midiStreamPause [WINMM.@] */ MMRESULT WINAPI midiStreamPause(HMIDISTRM hMidiStrm) { WINE_MIDIStream* lpMidiStrm; DWORD ret = MMSYSERR_NOERROR; TRACE("(%p)!\n", hMidiStrm); if (!MMSYSTEM_GetMidiStream(hMidiStrm, &lpMidiStrm, NULL)) return MMSYSERR_INVALHANDLE; PostThreadMessageA(lpMidiStrm->dwThreadID, WINE_MSM_PAUSE, 0, 0); return ret; } /************************************************************************** * midiStreamPosition [WINMM.@] */ MMRESULT WINAPI midiStreamPosition(HMIDISTRM hMidiStrm, LPMMTIME lpMMT, UINT cbmmt) { WINE_MIDIStream* lpMidiStrm; DWORD ret = MMSYSERR_NOERROR; TRACE("(%p, %p, %u)!\n", hMidiStrm, lpMMT, cbmmt); if (!MMSYSTEM_GetMidiStream(hMidiStrm, &lpMidiStrm, NULL)) { ret = MMSYSERR_INVALHANDLE; } else if (lpMMT == NULL || cbmmt != sizeof(MMTIME)) { ret = MMSYSERR_INVALPARAM; } else { switch (lpMMT->wType) { default: FIXME("Unsupported time type %x\n", lpMMT->wType); /* fall through */ case TIME_BYTES: case TIME_SAMPLES: lpMMT->wType = TIME_MS; /* fall through to alternative format */ case TIME_MS: lpMMT->u.ms = lpMidiStrm->dwPositionMS; TRACE("=> %d ms\n", lpMMT->u.ms); break; case TIME_TICKS: lpMMT->u.ticks = lpMidiStrm->dwPulses; TRACE("=> %d ticks\n", lpMMT->u.ticks); break; } } return ret; } /************************************************************************** * midiStreamProperty [WINMM.@] */ MMRESULT WINAPI midiStreamProperty(HMIDISTRM hMidiStrm, LPBYTE lpPropData, DWORD dwProperty) { WINE_MIDIStream* lpMidiStrm; MMRESULT ret = MMSYSERR_NOERROR; TRACE("(%p, %p, %x)\n", hMidiStrm, lpPropData, dwProperty); if (!MMSYSTEM_GetMidiStream(hMidiStrm, &lpMidiStrm, NULL)) { ret = MMSYSERR_INVALHANDLE; } else if ((dwProperty & (MIDIPROP_GET|MIDIPROP_SET)) == 0) { ret = MMSYSERR_INVALPARAM; } else if (dwProperty & MIDIPROP_TEMPO) { MIDIPROPTEMPO* mpt = (MIDIPROPTEMPO*)lpPropData; if (sizeof(MIDIPROPTEMPO) != mpt->cbStruct) { ret = MMSYSERR_INVALPARAM; } else if (dwProperty & MIDIPROP_SET) { lpMidiStrm->dwTempo = mpt->dwTempo; TRACE("Setting tempo to %d\n", mpt->dwTempo); } else if (dwProperty & MIDIPROP_GET) { mpt->dwTempo = lpMidiStrm->dwTempo; TRACE("Getting tempo <= %d\n", mpt->dwTempo); } } else if (dwProperty & MIDIPROP_TIMEDIV) { MIDIPROPTIMEDIV* mptd = (MIDIPROPTIMEDIV*)lpPropData; if (sizeof(MIDIPROPTIMEDIV) != mptd->cbStruct) { ret = MMSYSERR_INVALPARAM; } else if (dwProperty & MIDIPROP_SET) { lpMidiStrm->dwTimeDiv = mptd->dwTimeDiv; TRACE("Setting time div to %d\n", mptd->dwTimeDiv); } else if (dwProperty & MIDIPROP_GET) { mptd->dwTimeDiv = lpMidiStrm->dwTimeDiv; TRACE("Getting time div <= %d\n", mptd->dwTimeDiv); } } else { ret = MMSYSERR_INVALPARAM; } return ret; } /************************************************************************** * midiStreamRestart [WINMM.@] */ MMRESULT WINAPI midiStreamRestart(HMIDISTRM hMidiStrm) { WINE_MIDIStream* lpMidiStrm; MMRESULT ret = MMSYSERR_NOERROR; TRACE("(%p)!\n", hMidiStrm); if (!MMSYSTEM_GetMidiStream(hMidiStrm, &lpMidiStrm, NULL)) return MMSYSERR_INVALHANDLE; PostThreadMessageA(lpMidiStrm->dwThreadID, WINE_MSM_RESUME, 0, 0); return ret; } /************************************************************************** * midiStreamStop [WINMM.@] */ MMRESULT WINAPI midiStreamStop(HMIDISTRM hMidiStrm) { WINE_MIDIStream* lpMidiStrm; MMRESULT ret = MMSYSERR_NOERROR; TRACE("(%p)!\n", hMidiStrm); if (!MMSYSTEM_GetMidiStream(hMidiStrm, &lpMidiStrm, NULL)) return MMSYSERR_INVALHANDLE; PostThreadMessageA(lpMidiStrm->dwThreadID, WINE_MSM_STOP, 0, 0); return ret; } struct mm_starter { LPTASKCALLBACK cb; DWORD client; HANDLE event; }; static DWORD WINAPI mmTaskRun(void* pmt) { struct mm_starter mms; memcpy(&mms, pmt, sizeof(struct mm_starter)); HeapFree(GetProcessHeap(), 0, pmt); mms.cb(mms.client); if (mms.event) SetEvent(mms.event); return 0; } /****************************************************************** * mmTaskCreate (WINMM.@) */ UINT WINAPI mmTaskCreate(LPTASKCALLBACK cb, HANDLE* ph, DWORD_PTR client) { HANDLE hThread; HANDLE hEvent = 0; struct mm_starter *mms; mms = HeapAlloc(GetProcessHeap(), 0, sizeof(struct mm_starter)); if (mms == NULL) return TASKERR_OUTOFMEMORY; mms->cb = cb; mms->client = client; if (ph) hEvent = CreateEventW(NULL, FALSE, FALSE, NULL); mms->event = hEvent; hThread = CreateThread(0, 0, mmTaskRun, mms, 0, NULL); if (!hThread) { HeapFree(GetProcessHeap(), 0, mms); if (hEvent) CloseHandle(hEvent); return TASKERR_OUTOFMEMORY; } SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL); if (ph) *ph = hEvent; CloseHandle(hThread); return 0; } /****************************************************************** * mmTaskBlock (WINMM.@) */ VOID WINAPI mmTaskBlock(DWORD tid) { MSG msg; do { GetMessageA(&msg, 0, 0, 0); if (msg.hwnd) DispatchMessageA(&msg); } while (msg.message != WM_USER); } /****************************************************************** * mmTaskSignal (WINMM.@) */ BOOL WINAPI mmTaskSignal(DWORD tid) { return PostThreadMessageW(tid, WM_USER, 0, 0); } /****************************************************************** * mmTaskYield (WINMM.@) */ VOID WINAPI mmTaskYield(VOID) {} /****************************************************************** * mmGetCurrentTask (WINMM.@) */ DWORD WINAPI mmGetCurrentTask(VOID) { return GetCurrentThreadId(); }