| /* -*- 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 <stdio.h> | |
| #include <stdarg.h> | |
| #include <string.h> | |
| #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: | |
| midiOutLongMsg(lpMidiStrm->hDevice, lpMidiStrm->lpMidiHdr, MEVT_EVENTPARM(me->dwEvent)); | |
| 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(); | |
| } |