xref: /reactos/dll/win32/winmm/playsound.c (revision 4561998a)
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
2 
3 /*
4  * MMSYSTEM functions
5  *
6  * Copyright 1993      Martin Ayotte
7  *           1998-2002 Eric Pouech
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23 
24 #include "winemm.h"
25 
26 #include <winternl.h>
27 
28 WINE_DEFAULT_DEBUG_CHANNEL(winmm);
29 
30 typedef struct tagWINE_PLAYSOUND
31 {
32     unsigned                    bLoop : 1,
33                                 bAlloc : 1;
34     LPCWSTR                     pszSound;
35     HMODULE                     hMod;
36     DWORD                       fdwSound;
37     HANDLE                      hThread;
38     HWAVEOUT                    hWave;
39     struct tagWINE_PLAYSOUND*   lpNext;
40 } WINE_PLAYSOUND;
41 
42 static WINE_PLAYSOUND *PlaySoundList;
43 
44 static HMMIO	get_mmioFromFile(LPCWSTR lpszName)
45 {
46     HMMIO       ret;
47     WCHAR       buf[256];
48     LPWSTR      dummy;
49 
50     ret = mmioOpenW((LPWSTR)lpszName, NULL,
51                     MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
52     if (ret != 0) return ret;
53     if (SearchPathW(NULL, lpszName, NULL, sizeof(buf)/sizeof(buf[0]), buf, &dummy))
54     {
55         return mmioOpenW(buf, NULL,
56                          MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
57     }
58     return 0;
59 }
60 
61 static HMMIO	get_mmioFromProfile(UINT uFlags, LPCWSTR lpszName)
62 {
63     WCHAR	str[128];
64     LPWSTR	ptr;
65     HMMIO  	hmmio;
66     HKEY        hRegSnd, hRegApp, hScheme, hSnd;
67     DWORD       err, type, count;
68 
69     static const WCHAR  wszSounds[] = {'S','o','u','n','d','s',0};
70     static const WCHAR  wszDefault[] = {'D','e','f','a','u','l','t',0};
71     static const WCHAR  wszKey[] = {'A','p','p','E','v','e','n','t','s','\\',
72                                     'S','c','h','e','m','e','s','\\',
73                                     'A','p','p','s',0};
74     static const WCHAR  wszDotDefault[] = {'.','D','e','f','a','u','l','t',0};
75     static const WCHAR  wszDotCurrent[] = {'.','C','u','r','r','e','n','t',0};
76     static const WCHAR  wszNull[] = {0};
77 
78     TRACE("searching in SystemSound list for %s\n", debugstr_w(lpszName));
79     GetProfileStringW(wszSounds, lpszName, wszNull, str, sizeof(str)/sizeof(str[0]));
80     if (lstrlenW(str) == 0)
81     {
82 	if (uFlags & SND_NODEFAULT) goto next;
83 	GetProfileStringW(wszSounds, wszDefault, wszNull, str, sizeof(str)/sizeof(str[0]));
84 	if (lstrlenW(str) == 0) goto next;
85     }
86     for (ptr = str; *ptr && *ptr != ','; ptr++);
87     if (*ptr) *ptr = 0;
88     hmmio = mmioOpenW(str, NULL, MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
89     if (hmmio != 0) return hmmio;
90  next:
91     /* we look up the registry under
92      *      HKCU\AppEvents\Schemes\Apps\.Default
93      *      HKCU\AppEvents\Schemes\Apps\<AppName>
94      */
95     if (RegOpenKeyW(HKEY_CURRENT_USER, wszKey, &hRegSnd) != 0) goto none;
96     if (uFlags & SND_APPLICATION)
97     {
98         DWORD len;
99 
100         err = 1; /* error */
101         len = GetModuleFileNameW(0, str, sizeof(str)/sizeof(str[0]));
102         if (len > 0 && len < sizeof(str)/sizeof(str[0]))
103         {
104             for (ptr = str + lstrlenW(str) - 1; ptr >= str; ptr--)
105             {
106                 if (*ptr == '.') *ptr = 0;
107                 if (*ptr == '\\')
108                 {
109                     err = RegOpenKeyW(hRegSnd, ptr+1, &hRegApp);
110                     break;
111                 }
112             }
113         }
114     }
115     else
116     {
117         err = RegOpenKeyW(hRegSnd, wszDotDefault, &hRegApp);
118     }
119     RegCloseKey(hRegSnd);
120     if (err != 0) goto none;
121     err = RegOpenKeyW(hRegApp, lpszName, &hScheme);
122     RegCloseKey(hRegApp);
123     if (err != 0) goto none;
124     /* what's the difference between .Current and .Default ? */
125     err = RegOpenKeyW(hScheme, wszDotDefault, &hSnd);
126     if (err != 0)
127     {
128         err = RegOpenKeyW(hScheme, wszDotCurrent, &hSnd);
129         RegCloseKey(hScheme);
130         if (err != 0)
131             goto none;
132     }
133     count = sizeof(str);
134     err = RegQueryValueExW(hSnd, NULL, 0, &type, (LPBYTE)str, &count);
135     RegCloseKey(hSnd);
136     if (err != 0 || !*str) goto none;
137     hmmio = mmioOpenW(str, NULL, MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
138     if (hmmio) return hmmio;
139  none:
140     WARN("can't find SystemSound=%s !\n", debugstr_w(lpszName));
141     return 0;
142 }
143 
144 struct playsound_data
145 {
146     HANDLE	hEvent;
147     LONG	dwEventCount;
148 };
149 
150 static void CALLBACK PlaySound_Callback(HWAVEOUT hwo, UINT uMsg,
151 					DWORD_PTR dwInstance,
152 					DWORD_PTR dwParam1, DWORD_PTR dwParam2)
153 {
154     struct playsound_data*	s = (struct playsound_data*)dwInstance;
155 
156     switch (uMsg) {
157     case WOM_OPEN:
158     case WOM_CLOSE:
159 	break;
160     case WOM_DONE:
161 	InterlockedIncrement(&s->dwEventCount);
162 	TRACE("Returning waveHdr=%lx\n", dwParam1);
163 	SetEvent(s->hEvent);
164 	break;
165     default:
166 	ERR("Unknown uMsg=%d\n", uMsg);
167     }
168 }
169 
170 static void PlaySound_WaitDone(struct playsound_data* s)
171 {
172     for (;;) {
173 	ResetEvent(s->hEvent);
174 	if (InterlockedDecrement(&s->dwEventCount) >= 0) break;
175 	InterlockedIncrement(&s->dwEventCount);
176 
177 	WaitForSingleObject(s->hEvent, INFINITE);
178     }
179 }
180 
181 static BOOL PlaySound_IsString(DWORD fdwSound, const void* psz)
182 {
183     /* SND_RESOURCE is 0x40004 while
184      * SND_MEMORY is 0x00004
185      */
186     switch (fdwSound & (SND_RESOURCE|SND_ALIAS_ID|SND_FILENAME))
187     {
188     case SND_RESOURCE:  return HIWORD(psz) != 0; /* by name or by ID ? */
189     case SND_ALIAS_ID:
190     case SND_MEMORY:    return FALSE;
191     case SND_ALIAS:
192     case SND_FILENAME:
193     case 0:             return TRUE;
194     default:            FIXME("WTF\n"); return FALSE;
195     }
196 }
197 
198 static void     PlaySound_Free(WINE_PLAYSOUND* wps)
199 {
200     WINE_PLAYSOUND**    p;
201 
202     EnterCriticalSection(&WINMM_cs);
203     for (p = &PlaySoundList; *p && *p != wps; p = &((*p)->lpNext));
204     if (*p) *p = (*p)->lpNext;
205     if (PlaySoundList == NULL) SetEvent(psLastEvent);
206     LeaveCriticalSection(&WINMM_cs);
207     if (wps->bAlloc) HeapFree(GetProcessHeap(), 0, (void*)wps->pszSound);
208     if (wps->hThread) CloseHandle(wps->hThread);
209     HeapFree(GetProcessHeap(), 0, wps);
210 }
211 
212 static WINE_PLAYSOUND*  PlaySound_Alloc(const void* pszSound, HMODULE hmod,
213                                         DWORD fdwSound, BOOL bUnicode)
214 {
215     WINE_PLAYSOUND* wps;
216 
217     wps = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*wps));
218     if (!wps) return NULL;
219 
220     wps->hMod = hmod;
221     wps->fdwSound = fdwSound;
222     if (PlaySound_IsString(fdwSound, pszSound))
223     {
224         if (bUnicode)
225         {
226             if (fdwSound & SND_ASYNC)
227             {
228                 LPWSTR sound = HeapAlloc(GetProcessHeap(), 0,
229                                          (lstrlenW(pszSound)+1) * sizeof(WCHAR));
230                 if (!sound) goto oom_error;
231                 wps->pszSound = lstrcpyW(sound, pszSound);
232                 wps->bAlloc = TRUE;
233             }
234             else
235                 wps->pszSound = pszSound;
236         }
237         else
238         {
239             UNICODE_STRING usBuffer;
240             RtlCreateUnicodeStringFromAsciiz(&usBuffer, pszSound);
241             wps->pszSound = usBuffer.Buffer;
242             if (!wps->pszSound) goto oom_error;
243             wps->bAlloc = TRUE;
244         }
245     }
246     else
247         wps->pszSound = pszSound;
248 
249     return wps;
250  oom_error:
251     PlaySound_Free(wps);
252     return NULL;
253 }
254 
255 static DWORD WINAPI proc_PlaySound(LPVOID arg)
256 {
257     WINE_PLAYSOUND*     wps = arg;
258     BOOL		bRet = FALSE;
259     HMMIO		hmmio = 0;
260     MMCKINFO		ckMainRIFF;
261     MMCKINFO        	mmckInfo;
262     LPWAVEFORMATEX      lpWaveFormat = NULL;
263     LPWAVEHDR		waveHdr = NULL;
264     INT			count, bufsize, left, index;
265     struct playsound_data	s;
266     void*               data;
267 
268     s.hEvent = 0;
269 
270     TRACE("SoundName=%s !\n", debugstr_w(wps->pszSound));
271 
272     /* if resource, grab it */
273     if ((wps->fdwSound & SND_RESOURCE) == SND_RESOURCE) {
274         static const WCHAR wszWave[] = {'W','A','V','E',0};
275         HRSRC	hRes;
276         HGLOBAL	hGlob;
277 
278         if ((hRes = FindResourceW(wps->hMod, wps->pszSound, wszWave)) == 0 ||
279             (hGlob = LoadResource(wps->hMod, hRes)) == 0)
280             goto errCleanUp;
281         if ((data = LockResource(hGlob)) == NULL) {
282             FreeResource(hGlob);
283             goto errCleanUp;
284         }
285         FreeResource(hGlob);
286     } else
287         data = (void*)wps->pszSound;
288 
289     /* construct an MMIO stream (either in memory, or from a file */
290     if (wps->fdwSound & SND_MEMORY)
291     { /* NOTE: SND_RESOURCE has the SND_MEMORY bit set */
292 	MMIOINFO	mminfo;
293 
294 	memset(&mminfo, 0, sizeof(mminfo));
295 	mminfo.fccIOProc = FOURCC_MEM;
296 	mminfo.pchBuffer = data;
297 	mminfo.cchBuffer = -1; /* FIXME: when a resource, could grab real size */
298 	TRACE("Memory sound %p\n", data);
299 	hmmio = mmioOpenW(NULL, &mminfo, MMIO_READ);
300     }
301     else if (wps->fdwSound & SND_ALIAS)
302     {
303         if ((wps->fdwSound & SND_ALIAS_ID) == SND_ALIAS_ID)
304         {
305             static const WCHAR  wszSystemAsterisk[] = {'S','y','s','t','e','m','A','s','t','e','r','i','s','k',0};
306             static const WCHAR  wszSystemDefault[] = {'S','y','s','t','e','m','D','e','f','a','u','l','t',0};
307             static const WCHAR  wszSystemExclamation[] = {'S','y','s','t','e','m','E','x','c','l','a','m','a','t','i','o','n',0};
308             static const WCHAR  wszSystemExit[] = {'S','y','s','t','e','m','E','x','i','t',0};
309             static const WCHAR  wszSystemHand[] = {'S','y','s','t','e','m','H','a','n','d',0};
310             static const WCHAR  wszSystemQuestion[] = {'S','y','s','t','e','m','Q','u','e','s','t','i','o','n',0};
311             static const WCHAR  wszSystemStart[] = {'S','y','s','t','e','m','S','t','a','r','t',0};
312             static const WCHAR  wszSystemWelcome[] = {'S','y','s','t','e','m','W','e','l','c','o','m','e',0};
313 
314             wps->fdwSound &= ~(SND_ALIAS_ID ^ SND_ALIAS);
315             if (wps->pszSound == (LPCWSTR)SND_ALIAS_SYSTEMASTERISK)
316                 wps->pszSound = wszSystemAsterisk;
317             else if (wps->pszSound == (LPCWSTR)SND_ALIAS_SYSTEMDEFAULT)
318                 wps->pszSound = wszSystemDefault;
319             else if (wps->pszSound == (LPCWSTR)SND_ALIAS_SYSTEMEXCLAMATION)
320                 wps->pszSound = wszSystemExclamation;
321             else if (wps->pszSound == (LPCWSTR)SND_ALIAS_SYSTEMEXIT)
322                 wps->pszSound = wszSystemExit;
323             else if (wps->pszSound == (LPCWSTR)SND_ALIAS_SYSTEMHAND)
324                 wps->pszSound = wszSystemHand;
325             else if (wps->pszSound == (LPCWSTR)SND_ALIAS_SYSTEMQUESTION)
326                 wps->pszSound = wszSystemQuestion;
327             else if (wps->pszSound == (LPCWSTR)SND_ALIAS_SYSTEMSTART)
328                 wps->pszSound = wszSystemStart;
329             else if (wps->pszSound == (LPCWSTR)SND_ALIAS_SYSTEMWELCOME)
330                 wps->pszSound = wszSystemWelcome;
331             else goto errCleanUp;
332         }
333         hmmio = get_mmioFromProfile(wps->fdwSound, wps->pszSound);
334     }
335     else if (wps->fdwSound & SND_FILENAME)
336     {
337         hmmio = get_mmioFromFile(wps->pszSound);
338     }
339     else
340     {
341         if ((hmmio = get_mmioFromProfile(wps->fdwSound | SND_NODEFAULT, wps->pszSound)) == 0)
342         {
343             if ((hmmio = get_mmioFromFile(wps->pszSound)) == 0)
344             {
345                 hmmio = get_mmioFromProfile(wps->fdwSound, wps->pszSound);
346             }
347         }
348     }
349     if (hmmio == 0) goto errCleanUp;
350 
351     if (mmioDescend(hmmio, &ckMainRIFF, NULL, 0))
352 	goto errCleanUp;
353 
354     TRACE("ParentChunk ckid=%.4s fccType=%.4s cksize=%08X\n",
355 	  (LPSTR)&ckMainRIFF.ckid, (LPSTR)&ckMainRIFF.fccType, ckMainRIFF.cksize);
356 
357     if ((ckMainRIFF.ckid != FOURCC_RIFF) ||
358 	(ckMainRIFF.fccType != mmioFOURCC('W', 'A', 'V', 'E')))
359 	goto errCleanUp;
360 
361     mmckInfo.ckid = mmioFOURCC('f', 'm', 't', ' ');
362     if (mmioDescend(hmmio, &mmckInfo, &ckMainRIFF, MMIO_FINDCHUNK))
363 	goto errCleanUp;
364 
365     TRACE("Chunk Found ckid=%.4s fccType=%08x cksize=%08X\n",
366 	  (LPSTR)&mmckInfo.ckid, mmckInfo.fccType, mmckInfo.cksize);
367 
368     lpWaveFormat = HeapAlloc(GetProcessHeap(), 0, mmckInfo.cksize);
369     if (mmioRead(hmmio, (HPSTR)lpWaveFormat, mmckInfo.cksize) < sizeof(PCMWAVEFORMAT))
370 	goto errCleanUp;
371 
372     TRACE("wFormatTag=%04X !\n", 	lpWaveFormat->wFormatTag);
373     TRACE("nChannels=%d\n", 		lpWaveFormat->nChannels);
374     TRACE("nSamplesPerSec=%d\n", 	lpWaveFormat->nSamplesPerSec);
375     TRACE("nAvgBytesPerSec=%d\n", 	lpWaveFormat->nAvgBytesPerSec);
376     TRACE("nBlockAlign=%d\n", 		lpWaveFormat->nBlockAlign);
377     TRACE("wBitsPerSample=%u !\n", 	lpWaveFormat->wBitsPerSample);
378 
379     /* move to end of 'fmt ' chunk */
380     mmioAscend(hmmio, &mmckInfo, 0);
381 
382     mmckInfo.ckid = mmioFOURCC('d', 'a', 't', 'a');
383     if (mmioDescend(hmmio, &mmckInfo, &ckMainRIFF, MMIO_FINDCHUNK))
384 	goto errCleanUp;
385 
386     TRACE("Chunk Found ckid=%.4s fccType=%08x cksize=%08X\n",
387 	  (LPSTR)&mmckInfo.ckid, mmckInfo.fccType, mmckInfo.cksize);
388 
389     s.hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
390 
391     if (waveOutOpen(&wps->hWave, WAVE_MAPPER, lpWaveFormat, (DWORD_PTR)PlaySound_Callback,
392 		    (DWORD_PTR)&s, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
393 	goto errCleanUp;
394 
395     /* make it so that 3 buffers per second are needed */
396     bufsize = (((lpWaveFormat->nAvgBytesPerSec / 3) - 1) / lpWaveFormat->nBlockAlign + 1) *
397 	lpWaveFormat->nBlockAlign;
398     waveHdr = HeapAlloc(GetProcessHeap(), 0, 2 * sizeof(WAVEHDR) + 2 * bufsize);
399     waveHdr[0].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR);
400     waveHdr[1].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR) + bufsize;
401     waveHdr[0].dwUser = waveHdr[1].dwUser = 0L;
402     waveHdr[0].dwLoops = waveHdr[1].dwLoops = 0L;
403     waveHdr[0].dwFlags = waveHdr[1].dwFlags = 0L;
404     waveHdr[0].dwBufferLength = waveHdr[1].dwBufferLength = bufsize;
405     if (waveOutPrepareHeader(wps->hWave, &waveHdr[0], sizeof(WAVEHDR)) ||
406 	waveOutPrepareHeader(wps->hWave, &waveHdr[1], sizeof(WAVEHDR))) {
407 	goto errCleanUp;
408     }
409 
410     s.dwEventCount = 1L; /* for first buffer */
411     index = 0;
412 
413     do {
414 	left = mmckInfo.cksize;
415 
416 	mmioSeek(hmmio, mmckInfo.dwDataOffset, SEEK_SET);
417 	while (left)
418         {
419 	    if (WaitForSingleObject(psStopEvent, 0) == WAIT_OBJECT_0)
420             {
421 		wps->bLoop = FALSE;
422 		break;
423 	    }
424 	    count = mmioRead(hmmio, waveHdr[index].lpData, min(bufsize, left));
425 	    if (count < 1) break;
426 	    left -= count;
427 	    waveHdr[index].dwBufferLength = count;
428 	    waveHdr[index].dwFlags &= ~WHDR_DONE;
429 	    if (waveOutWrite(wps->hWave, &waveHdr[index], sizeof(WAVEHDR)) == MMSYSERR_NOERROR) {
430                 index ^= 1;
431                 PlaySound_WaitDone(&s);
432             }
433             else FIXME("Couldn't play header\n");
434 	}
435 	bRet = TRUE;
436     } while (wps->bLoop);
437 
438     PlaySound_WaitDone(&s); /* for last buffer */
439     waveOutReset(wps->hWave);
440 
441     waveOutUnprepareHeader(wps->hWave, &waveHdr[0], sizeof(WAVEHDR));
442     waveOutUnprepareHeader(wps->hWave, &waveHdr[1], sizeof(WAVEHDR));
443 
444 errCleanUp:
445     TRACE("Done playing=%s => %s!\n", debugstr_w(wps->pszSound), bRet ? "ok" : "ko");
446     CloseHandle(s.hEvent);
447     HeapFree(GetProcessHeap(), 0, waveHdr);
448     HeapFree(GetProcessHeap(), 0, lpWaveFormat);
449     if (wps->hWave)	while (waveOutClose(wps->hWave) == WAVERR_STILLPLAYING) Sleep(100);
450     if (hmmio) 		mmioClose(hmmio, 0);
451 
452     PlaySound_Free(wps);
453 
454     return bRet;
455 }
456 
457 static BOOL MULTIMEDIA_PlaySound(const void* pszSound, HMODULE hmod, DWORD fdwSound, BOOL bUnicode)
458 {
459     WINE_PLAYSOUND*     wps = NULL;
460 
461     TRACE("pszSound='%p' hmod=%p fdwSound=%08X\n",
462 	  pszSound, hmod, fdwSound);
463 
464     /* FIXME? I see no difference between SND_NOWAIT and SND_NOSTOP !
465      * there could be one if several sounds can be played at once...
466      */
467     if ((fdwSound & (SND_NOWAIT | SND_NOSTOP)) && PlaySoundList != NULL)
468 	return FALSE;
469 
470     /* alloc internal structure, if we need to play something */
471     if (pszSound && !(fdwSound & SND_PURGE))
472     {
473         if (!(wps = PlaySound_Alloc(pszSound, hmod, fdwSound, bUnicode)))
474             return FALSE;
475     }
476 
477     EnterCriticalSection(&WINMM_cs);
478     /* since several threads can enter PlaySound in parallel, we're not
479      * sure, at this point, that another thread didn't start a new playsound
480      */
481     while (PlaySoundList != NULL)
482     {
483         ResetEvent(psLastEvent);
484         /* FIXME: doc says we have to stop all instances of pszSound if it's non
485          * NULL... as of today, we stop all playing instances */
486         SetEvent(psStopEvent);
487 
488         waveOutReset(PlaySoundList->hWave);
489         LeaveCriticalSection(&WINMM_cs);
490         WaitForSingleObject(psLastEvent, INFINITE);
491         EnterCriticalSection(&WINMM_cs);
492 
493         ResetEvent(psStopEvent);
494     }
495 
496     if (wps) wps->lpNext = PlaySoundList;
497     PlaySoundList = wps;
498     LeaveCriticalSection(&WINMM_cs);
499 
500     if (!pszSound || (fdwSound & SND_PURGE)) return TRUE;
501 
502     if (fdwSound & SND_ASYNC)
503     {
504         DWORD       id;
505         HANDLE      handle;
506         wps->bLoop = (fdwSound & SND_LOOP) ? TRUE : FALSE;
507         if ((handle = CreateThread(NULL, 0, proc_PlaySound, wps, 0, &id)) != 0) {
508             wps->hThread = handle;
509             SetThreadPriority(handle, THREAD_PRIORITY_TIME_CRITICAL);
510             return TRUE;
511         }
512     }
513     else return proc_PlaySound(wps);
514 
515     /* error cases */
516     PlaySound_Free(wps);
517     return FALSE;
518 }
519 
520 /**************************************************************************
521  * 				PlaySoundA		[WINMM.@]
522  */
523 BOOL WINAPI PlaySoundA(LPCSTR pszSoundA, HMODULE hmod, DWORD fdwSound)
524 {
525     return MULTIMEDIA_PlaySound(pszSoundA, hmod, fdwSound, FALSE);
526 }
527 
528 /**************************************************************************
529  * 				PlaySoundW		[WINMM.@]
530  */
531 BOOL WINAPI PlaySoundW(LPCWSTR pszSoundW, HMODULE hmod, DWORD fdwSound)
532 {
533     return MULTIMEDIA_PlaySound(pszSoundW, hmod, fdwSound, TRUE);
534 }
535 
536 /**************************************************************************
537  * 				sndPlaySoundA		[WINMM.@]
538  */
539 BOOL WINAPI sndPlaySoundA(LPCSTR pszSoundA, UINT uFlags)
540 {
541     uFlags &= SND_ALIAS_ID|SND_FILENAME|SND_ASYNC|SND_LOOP|SND_MEMORY|SND_NODEFAULT|SND_NOSTOP|SND_SYNC;
542     return MULTIMEDIA_PlaySound(pszSoundA, 0, uFlags, FALSE);
543 }
544 
545 /**************************************************************************
546  * 				sndPlaySoundW		[WINMM.@]
547  */
548 BOOL WINAPI sndPlaySoundW(LPCWSTR pszSound, UINT uFlags)
549 {
550     uFlags &= SND_ALIAS_ID|SND_FILENAME|SND_ASYNC|SND_LOOP|SND_MEMORY|SND_NODEFAULT|SND_NOSTOP|SND_SYNC;
551     return MULTIMEDIA_PlaySound(pszSound, 0, uFlags, TRUE);
552 }
553 
554 /**************************************************************************
555  * 				mmsystemGetVersion	[WINMM.@]
556  */
557 UINT WINAPI mmsystemGetVersion(void)
558 {
559     TRACE("3.10 (Win95?)\n");
560     return 0x030a;
561 }
562