xref: /reactos/dll/win32/winmm/playsound.c (revision f7d612f3)
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     HMMIO                       hmmio;
34     HWAVEOUT                    hWave;
35 } WINE_PLAYSOUND;
36 
37 static WINE_PLAYSOUND *PlaySoundCurrent;
38 static BOOL bPlaySoundStop;
39 
40 static HMMIO    get_mmioFromFile(LPCWSTR lpszName)
41 {
42     HMMIO       ret;
43     WCHAR       buf[256];
44     LPWSTR      dummy;
45 
46     ret = mmioOpenW((LPWSTR)lpszName, NULL,
47                     MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
48     if (ret != 0) return ret;
49     if (SearchPathW(NULL, lpszName, L".wav", ARRAY_SIZE(buf), buf, &dummy))
50     {
51         return mmioOpenW(buf, NULL,
52                          MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
53     }
54     return 0;
55 }
56 
57 static HMMIO get_mmioFromProfile(UINT uFlags, LPCWSTR lpszName)
58 {
59     WCHAR str[128];
60     LPWSTR ptr, pszSnd;
61     HMMIO hmmio;
62     HKEY hUserKey, hRegSnd, hRegApp, hScheme, hSnd;
63     DWORD err, type, count;
64     BOOL bIsDefault;
65 
66     TRACE("searching in SystemSound list for %s\n", debugstr_w(lpszName));
67 
68     bIsDefault = (_wcsicmp(lpszName, L"SystemDefault") == 0);
69 
70     GetProfileStringW(L"Sounds",
71                       bIsDefault ? L"Default" : lpszName,
72                       L"",
73                       str,
74                       ARRAY_SIZE(str));
75     if (!*str)
76         goto Next;
77 
78     for (ptr = str; *ptr && *ptr != L','; ptr++);
79 
80     if (*ptr)
81         *ptr = UNICODE_NULL;
82 
83     hmmio = mmioOpenW(str, NULL, MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
84     if (hmmio)
85         return hmmio;
86 
87 Next:
88     /* we look up the registry under
89      *      HKCU\AppEvents\Schemes\Apps\.Default
90      *      HKCU\AppEvents\Schemes\Apps\<AppName>
91      */
92     err = RegOpenCurrentUser(KEY_READ, &hUserKey);
93     if (err == ERROR_SUCCESS)
94     {
95         err = RegOpenKeyW(hUserKey, L"AppEvents\\Schemes\\Apps", &hRegSnd);
96         RegCloseKey(hUserKey);
97     }
98 
99     if (err != ERROR_SUCCESS)
100         goto None;
101 
102     if (uFlags & SND_APPLICATION)
103     {
104         DWORD len;
105 
106         err = ERROR_FILE_NOT_FOUND; /* error */
107         len = GetModuleFileNameW(NULL, str, ARRAY_SIZE(str));
108         if (len > 0 && len < ARRAY_SIZE(str))
109         {
110             for (ptr = str + lstrlenW(str) - 1; ptr >= str; ptr--)
111             {
112                 if (*ptr == L'.')
113                     *ptr = UNICODE_NULL;
114 
115                 if (*ptr == L'\\')
116                 {
117                     err = RegOpenKeyW(hRegSnd, ptr + 1, &hRegApp);
118                     break;
119                 }
120             }
121         }
122     }
123     else
124     {
125         err = RegOpenKeyW(hRegSnd, L".Default", &hRegApp);
126     }
127 
128     RegCloseKey(hRegSnd);
129 
130     if (err != ERROR_SUCCESS)
131         goto None;
132 
133     err = RegOpenKeyW(hRegApp,
134                       bIsDefault ? L".Default" : lpszName,
135                       &hScheme);
136 
137     RegCloseKey(hRegApp);
138 
139     if (err != ERROR_SUCCESS)
140         goto None;
141 
142     err = RegOpenKeyW(hScheme, L".Current", &hSnd);
143 
144     RegCloseKey(hScheme);
145 
146     if (err != ERROR_SUCCESS)
147         goto None;
148 
149     count = sizeof(str);
150     err = RegQueryValueExW(hSnd, NULL, 0, &type, (LPBYTE)str, &count);
151 
152     RegCloseKey(hSnd);
153 
154     if (err != ERROR_SUCCESS || !*str)
155         goto None;
156 
157     if (type == REG_EXPAND_SZ)
158     {
159         count = ExpandEnvironmentStringsW(str, NULL, 0);
160         if (count == 0)
161             goto None;
162 
163         pszSnd = HeapAlloc(GetProcessHeap(), 0, count * sizeof(WCHAR));
164         if (!pszSnd)
165             goto None;
166 
167         if (ExpandEnvironmentStringsW(str, pszSnd, count) == 0)
168         {
169             HeapFree(GetProcessHeap(), 0, pszSnd);
170             goto None;
171         }
172     }
173     else if (type == REG_SZ)
174     {
175         /* The type is REG_SZ, no need to expand */
176         pszSnd = str;
177     }
178     else
179     {
180         /* Invalid type */
181         goto None;
182     }
183 
184     hmmio = mmioOpenW(pszSnd, NULL, MMIO_ALLOCBUF | MMIO_READ | MMIO_DENYWRITE);
185 
186     if (type == REG_EXPAND_SZ)
187         HeapFree(GetProcessHeap(), 0, pszSnd);
188 
189     if (hmmio)
190         return hmmio;
191 
192 None:
193     WARN("can't find SystemSound=%s !\n", debugstr_w(lpszName));
194     return NULL;
195 }
196 
197 static HMMIO PlaySound_GetMMIO(LPCWSTR pszSound, HMODULE hMod, DWORD fdwSound)
198 {
199     BOOL bIsDefault = FALSE;
200     HMMIO hmmio = NULL;
201 
202     TRACE("SoundName=%s !\n", debugstr_w(pszSound));
203 
204     if (fdwSound & SND_MEMORY)
205     {
206         PVOID data;
207         MMIOINFO mminfo;
208 
209         /* NOTE: SND_RESOURCE has the SND_MEMORY bit set */
210         if ((fdwSound & SND_RESOURCE) == SND_RESOURCE)
211         {
212             HRSRC hRes;
213             HGLOBAL hGlob;
214 
215             hRes = FindResourceW(hMod, pszSound, L"WAVE");
216             hGlob = LoadResource(hMod, hRes);
217             if (!hRes || !hGlob)
218                 goto Quit;
219 
220             data = LockResource(hGlob);
221             FreeResource(hGlob);
222             if (!data)
223                 goto Quit;
224         }
225         else
226         {
227             data = (PVOID)pszSound;
228         }
229 
230         ZeroMemory(&mminfo, sizeof(mminfo));
231         mminfo.fccIOProc = FOURCC_MEM;
232         mminfo.pchBuffer = data;
233         mminfo.cchBuffer = -1; /* FIXME: when a resource, could grab real size */
234 
235         TRACE("Memory sound %p\n", data);
236 
237         hmmio = mmioOpenW(NULL, &mminfo, MMIO_READ);
238     }
239     else if (fdwSound & SND_ALIAS)
240     {
241         LPCWSTR pszName;
242 
243         /* NOTE: SND_ALIAS_ID has the SND_ALIAS bit set */
244         if ((fdwSound & SND_ALIAS_ID) == SND_ALIAS_ID)
245         {
246             if (pszSound == (LPCWSTR)SND_ALIAS_SYSTEMASTERISK)
247                 pszName = L"SystemAsterisk";
248             else if (pszSound == (LPCWSTR)SND_ALIAS_SYSTEMDEFAULT)
249                 pszName = L"SystemDefault";
250             else if (pszSound == (LPCWSTR)SND_ALIAS_SYSTEMEXCLAMATION)
251                 pszName = L"SystemExclamation";
252             else if (pszSound == (LPCWSTR)SND_ALIAS_SYSTEMEXIT)
253                 pszName = L"SystemExit";
254             else if (pszSound == (LPCWSTR)SND_ALIAS_SYSTEMHAND)
255                 pszName = L"SystemHand";
256             else if (pszSound == (LPCWSTR)SND_ALIAS_SYSTEMQUESTION)
257                 pszName = L"SystemQuestion";
258             else if (pszSound == (LPCWSTR)SND_ALIAS_SYSTEMSTART)
259                 pszName = L"SystemStart";
260             else if (pszSound == (LPCWSTR)SND_ALIAS_SYSTEMWELCOME)
261                 pszName = L"SystemWelcome";
262             else
263                 goto Quit;
264         }
265         else
266         {
267             pszName = pszSound;
268         }
269 
270         bIsDefault = (_wcsicmp(pszName, L"SystemDefault") == 0);
271         hmmio = get_mmioFromProfile(fdwSound, pszName);
272     }
273     else if (fdwSound & SND_FILENAME)
274     {
275         hmmio = get_mmioFromFile(pszSound);
276     }
277     else
278     {
279         hmmio = get_mmioFromProfile(fdwSound, pszSound);
280         if (!hmmio)
281             hmmio = get_mmioFromFile(pszSound);
282     }
283 
284 Quit:
285     if (!hmmio && !(fdwSound & SND_NODEFAULT))
286     {
287         if (fdwSound & SND_APPLICATION)
288         {
289             if (!bIsDefault)
290             {
291                 /* Find application-defined default sound */
292                 hmmio = get_mmioFromProfile(fdwSound, L"SystemDefault");
293                 if (hmmio)
294                     return hmmio;
295             }
296 
297             /* Find system default sound */
298             hmmio = get_mmioFromProfile(fdwSound & ~SND_APPLICATION, L"SystemDefault");
299         }
300         else if (!bIsDefault)
301         {
302             hmmio = get_mmioFromProfile(fdwSound, L"SystemDefault");
303         }
304     }
305 
306     return hmmio;
307 }
308 
309 struct playsound_data
310 {
311     HANDLE  hEvent;
312     LONG    dwEventCount;
313 };
314 
315 static void CALLBACK PlaySound_Callback(HWAVEOUT hwo, UINT uMsg,
316                                         DWORD_PTR dwInstance,
317                                         DWORD_PTR dwParam1, DWORD_PTR dwParam2)
318 {
319     struct playsound_data*  s = (struct playsound_data*)dwInstance;
320 
321     switch (uMsg) {
322     case WOM_OPEN:
323     case WOM_CLOSE:
324         break;
325     case WOM_DONE:
326         InterlockedIncrement(&s->dwEventCount);
327         TRACE("Returning waveHdr=%lx\n", dwParam1);
328         SetEvent(s->hEvent);
329         break;
330     default:
331         ERR("Unknown uMsg=%d\n", uMsg);
332     }
333 }
334 
335 static void PlaySound_WaitDone(struct playsound_data* s)
336 {
337     for (;;) {
338         if (InterlockedDecrement(&s->dwEventCount) >= 0) break;
339         InterlockedIncrement(&s->dwEventCount);
340 
341         WaitForSingleObject(s->hEvent, INFINITE);
342     }
343 }
344 
345 static BOOL PlaySound_IsString(DWORD fdwSound, const void* psz)
346 {
347     /* SND_RESOURCE is 0x40004 while
348      * SND_MEMORY is 0x00004
349      */
350     switch (fdwSound & (SND_RESOURCE | SND_ALIAS_ID | SND_FILENAME))
351     {
352         case SND_RESOURCE:
353             return HIWORD(psz) != 0; /* by name or by ID ? */
354 
355         case SND_ALIAS_ID:
356         case SND_MEMORY:
357             return FALSE;
358 
359         case SND_ALIAS:
360         case SND_FILENAME:
361         case 0:
362             return TRUE;
363 
364         default:
365             FIXME("WTF\n");
366             return FALSE;
367     }
368 }
369 
370 static void     PlaySound_Free(WINE_PLAYSOUND* wps)
371 {
372     EnterCriticalSection(&WINMM_cs);
373     PlaySoundCurrent = NULL;
374     SetEvent(psLastEvent);
375     LeaveCriticalSection(&WINMM_cs);
376     if (wps->hmmio) mmioClose(wps->hmmio, 0);
377     HeapFree(GetProcessHeap(), 0, wps);
378 }
379 
380 static WINE_PLAYSOUND* PlaySound_AllocAndGetMMIO(const void* pszSound, HMODULE hmod,
381                                                  DWORD fdwSound, BOOL bUnicode)
382 {
383     BOOL bIsString;
384     LPCWSTR pszSoundW;
385     UNICODE_STRING usBuffer;
386     WINE_PLAYSOUND* wps;
387 
388     bIsString = PlaySound_IsString(fdwSound, pszSound);
389 
390     if (bIsString && !bUnicode)
391     {
392         RtlCreateUnicodeStringFromAsciiz(&usBuffer, pszSound);
393         if (!usBuffer.Buffer)
394             return NULL;
395 
396         pszSoundW = usBuffer.Buffer;
397     }
398     else
399     {
400         pszSoundW = pszSound;
401     }
402 
403     wps = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*wps));
404     if (wps)
405     {
406         /* construct an MMIO stream (either in memory, or from a file) */
407         wps->hmmio = PlaySound_GetMMIO(pszSoundW, hmod, fdwSound);
408         if (!wps->hmmio)
409         {
410             PlaySound_Free(wps);
411             wps = NULL;
412         }
413     }
414 
415     if (bIsString && !bUnicode)
416         RtlFreeUnicodeString(&usBuffer);
417 
418     return wps;
419 }
420 
421 static BOOL proc_PlaySound(WINE_PLAYSOUND *wps)
422 {
423     BOOL                bRet = FALSE;
424     MMCKINFO            ckMainRIFF;
425     MMCKINFO            mmckInfo;
426     LPWAVEFORMATEX      lpWaveFormat = NULL;
427     HWAVEOUT            hWave = 0;
428     LPWAVEHDR           waveHdr = NULL;
429     INT                 count, bufsize, left, index;
430     struct playsound_data       s;
431     LONG                r;
432 
433     s.hEvent = 0;
434 
435     if (mmioDescend(wps->hmmio, &ckMainRIFF, NULL, 0))
436         goto errCleanUp;
437 
438     TRACE("ParentChunk ckid=%.4s fccType=%.4s cksize=%08X\n",
439           (LPSTR)&ckMainRIFF.ckid, (LPSTR)&ckMainRIFF.fccType, ckMainRIFF.cksize);
440 
441     if ((ckMainRIFF.ckid != FOURCC_RIFF) ||
442         (ckMainRIFF.fccType != mmioFOURCC('W', 'A', 'V', 'E')))
443         goto errCleanUp;
444 
445     mmckInfo.ckid = mmioFOURCC('f', 'm', 't', ' ');
446     if (mmioDescend(wps->hmmio, &mmckInfo, &ckMainRIFF, MMIO_FINDCHUNK))
447         goto errCleanUp;
448 
449     TRACE("Chunk Found ckid=%.4s fccType=%08x cksize=%08X\n",
450           (LPSTR)&mmckInfo.ckid, mmckInfo.fccType, mmckInfo.cksize);
451 
452     lpWaveFormat = HeapAlloc(GetProcessHeap(), 0, mmckInfo.cksize);
453     if (!lpWaveFormat)
454         goto errCleanUp;
455     r = mmioRead(wps->hmmio, (HPSTR)lpWaveFormat, mmckInfo.cksize);
456     if (r < 0 || r < sizeof(PCMWAVEFORMAT))
457         goto errCleanUp;
458 
459     TRACE("wFormatTag=%04X !\n",    lpWaveFormat->wFormatTag);
460     TRACE("nChannels=%d\n",         lpWaveFormat->nChannels);
461     TRACE("nSamplesPerSec=%d\n",    lpWaveFormat->nSamplesPerSec);
462     TRACE("nAvgBytesPerSec=%d\n",   lpWaveFormat->nAvgBytesPerSec);
463     TRACE("nBlockAlign=%d\n",       lpWaveFormat->nBlockAlign);
464     TRACE("wBitsPerSample=%u !\n",  lpWaveFormat->wBitsPerSample);
465 
466     /* move to end of 'fmt ' chunk */
467     mmioAscend(wps->hmmio, &mmckInfo, 0);
468 
469     mmckInfo.ckid = mmioFOURCC('d', 'a', 't', 'a');
470     if (mmioDescend(wps->hmmio, &mmckInfo, &ckMainRIFF, MMIO_FINDCHUNK))
471         goto errCleanUp;
472 
473     TRACE("Chunk Found ckid=%.4s fccType=%08x cksize=%08X\n",
474           (LPSTR)&mmckInfo.ckid, mmckInfo.fccType, mmckInfo.cksize);
475 
476     s.hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
477     if (!s.hEvent || bPlaySoundStop)
478         goto errCleanUp;
479 
480     if (waveOutOpen(&hWave, WAVE_MAPPER, lpWaveFormat, (DWORD_PTR)PlaySound_Callback,
481                     (DWORD_PTR)&s, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
482         goto errCleanUp;
483 
484     /* make it so that 3 buffers per second are needed */
485     bufsize = (((lpWaveFormat->nAvgBytesPerSec / 3) - 1) / lpWaveFormat->nBlockAlign + 1) *
486         lpWaveFormat->nBlockAlign;
487     waveHdr = HeapAlloc(GetProcessHeap(), 0, 2 * sizeof(WAVEHDR) + 2 * bufsize);
488     if (!waveHdr)
489         goto errCleanUp;
490     waveHdr[0].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR);
491     waveHdr[1].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR) + bufsize;
492     waveHdr[0].dwUser = waveHdr[1].dwUser = 0L;
493     waveHdr[0].dwLoops = waveHdr[1].dwLoops = 0L;
494     waveHdr[0].dwFlags = waveHdr[1].dwFlags = 0L;
495     waveHdr[0].dwBufferLength = waveHdr[1].dwBufferLength = bufsize;
496     if (waveOutPrepareHeader(hWave, &waveHdr[0], sizeof(WAVEHDR)) ||
497         waveOutPrepareHeader(hWave, &waveHdr[1], sizeof(WAVEHDR))) {
498         goto errCleanUp;
499     }
500 
501     wps->hWave = hWave;
502     s.dwEventCount = 1L; /* for first buffer */
503     index = 0;
504 
505     do {
506         left = mmckInfo.cksize;
507 
508         mmioSeek(wps->hmmio, mmckInfo.dwDataOffset, SEEK_SET);
509         while (left)
510         {
511             if (bPlaySoundStop)
512             {
513                 wps->bLoop = FALSE;
514                 break;
515             }
516             count = mmioRead(wps->hmmio, waveHdr[index].lpData, min(bufsize, left));
517             if (count < 1) break;
518             left -= count;
519             waveHdr[index].dwBufferLength = count;
520             if (waveOutWrite(hWave, &waveHdr[index], sizeof(WAVEHDR)) == MMSYSERR_NOERROR) {
521                 index ^= 1;
522                 PlaySound_WaitDone(&s);
523             }
524             else {
525                 ERR("Aborting play loop, waveOutWrite error\n");
526                 wps->bLoop = FALSE;
527                 break;
528             }
529         }
530         bRet = TRUE;
531     } while (wps->bLoop);
532 
533     PlaySound_WaitDone(&s); /* to balance first buffer */
534     waveOutReset(hWave);
535 
536     waveOutUnprepareHeader(hWave, &waveHdr[0], sizeof(WAVEHDR));
537     waveOutUnprepareHeader(hWave, &waveHdr[1], sizeof(WAVEHDR));
538 
539 errCleanUp:
540     TRACE("Done playing sound => %s!\n", bRet ? "ok" : "ko");
541     HeapFree(GetProcessHeap(), 0, lpWaveFormat);
542     if (hWave)
543     {
544         EnterCriticalSection(&WINMM_cs);
545         /* the CS prevents a concurrent waveOutReset */
546         wps->hWave = 0;
547         LeaveCriticalSection(&WINMM_cs);
548         while (waveOutClose(hWave) == WAVERR_STILLPLAYING)
549             Sleep(100);
550     }
551     CloseHandle(s.hEvent);
552     HeapFree(GetProcessHeap(), 0, waveHdr);
553 
554     PlaySound_Free(wps);
555 
556     return bRet;
557 }
558 
559 static DWORD WINAPI PlaySoundAsyncThreadProc(LPVOID lpParameter)
560 {
561     WINE_PLAYSOUND *wps = (WINE_PLAYSOUND*)lpParameter;
562 
563     /* Play the sound */
564     proc_PlaySound(wps);
565 
566     return 0;
567 }
568 
569 static BOOL proc_PlaySoundAsync(WINE_PLAYSOUND *wps)
570 {
571     HANDLE hThread;
572 
573     /* Create a thread to play the sound asynchronously */
574     hThread = CreateThread(NULL, 0, PlaySoundAsyncThreadProc, wps, 0, NULL);
575     if (hThread)
576     {
577         SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL);
578         CloseHandle(hThread);
579         return TRUE;
580     }
581 
582     /* Error cases */
583     PlaySound_Free(wps);
584     return FALSE;
585 }
586 
587 static BOOL MULTIMEDIA_PlaySound(const void* pszSound, HMODULE hmod, DWORD fdwSound, BOOL bUnicode)
588 {
589     WINE_PLAYSOUND*     wps = NULL;
590 
591     TRACE("pszSound='%p' hmod=%p fdwSound=%08X\n",
592           pszSound, hmod, fdwSound);
593 
594     /* SND_NOWAIT is ignored in w95/2k/xp. */
595     if ((fdwSound & SND_NOSTOP) && PlaySoundCurrent != NULL)
596         return FALSE;
597 
598     /* alloc internal structure, if we need to play something */
599     if (pszSound && !(fdwSound & SND_PURGE))
600     {
601         if (!(wps = PlaySound_AllocAndGetMMIO(pszSound, hmod, fdwSound, bUnicode)))
602             return FALSE;
603     }
604 
605     EnterCriticalSection(&WINMM_cs);
606     /* since several threads can enter PlaySound in parallel, we're not
607      * sure, at this point, that another thread didn't start a new playsound
608      */
609     while (PlaySoundCurrent != NULL)
610     {
611         ResetEvent(psLastEvent);
612         /* FIXME: doc says we have to stop all instances of pszSound if it's non
613          * NULL... as of today, we stop all playing instances */
614         bPlaySoundStop = TRUE;
615         if (PlaySoundCurrent->hWave)
616             waveOutReset(PlaySoundCurrent->hWave);
617 
618         LeaveCriticalSection(&WINMM_cs);
619         WaitForSingleObject(psLastEvent, INFINITE);
620         EnterCriticalSection(&WINMM_cs);
621 
622         bPlaySoundStop = FALSE;
623     }
624 
625     PlaySoundCurrent = wps;
626     LeaveCriticalSection(&WINMM_cs);
627 
628     if (!wps) return TRUE;
629 
630     if (fdwSound & SND_ASYNC)
631     {
632         wps->bLoop = (fdwSound & SND_LOOP) ? TRUE : FALSE;
633 
634         return proc_PlaySoundAsync(wps);
635     }
636 
637     return proc_PlaySound(wps);
638 }
639 
640 /**************************************************************************
641  * 				PlaySoundA		[WINMM.@]
642  */
643 BOOL WINAPI PlaySoundA(LPCSTR pszSoundA, HMODULE hmod, DWORD fdwSound)
644 {
645     return MULTIMEDIA_PlaySound(pszSoundA, hmod, fdwSound, FALSE);
646 }
647 
648 /**************************************************************************
649  * 				PlaySoundW		[WINMM.@]
650  */
651 BOOL WINAPI PlaySoundW(LPCWSTR pszSoundW, HMODULE hmod, DWORD fdwSound)
652 {
653     return MULTIMEDIA_PlaySound(pszSoundW, hmod, fdwSound, TRUE);
654 }
655 
656 /**************************************************************************
657  * 				sndPlaySoundA		[WINMM.@]
658  */
659 BOOL WINAPI sndPlaySoundA(LPCSTR pszSoundA, UINT uFlags)
660 {
661     uFlags &= SND_RESOURCE|SND_ALIAS_ID|SND_FILENAME|SND_ASYNC|SND_LOOP|SND_MEMORY|SND_NODEFAULT|SND_NOSTOP|SND_SYNC;
662     return MULTIMEDIA_PlaySound(pszSoundA, 0, uFlags, FALSE);
663 }
664 
665 /**************************************************************************
666  * 				sndPlaySoundW		[WINMM.@]
667  */
668 BOOL WINAPI sndPlaySoundW(LPCWSTR pszSound, UINT uFlags)
669 {
670     uFlags &= SND_RESOURCE|SND_ALIAS_ID|SND_FILENAME|SND_ASYNC|SND_LOOP|SND_MEMORY|SND_NODEFAULT|SND_NOSTOP|SND_SYNC;
671     return MULTIMEDIA_PlaySound(pszSound, 0, uFlags, TRUE);
672 }
673 
674 /**************************************************************************
675  * 				mmsystemGetVersion	[WINMM.@]
676  */
677 UINT WINAPI mmsystemGetVersion(void)
678 {
679     TRACE("3.10 (Win95?)\n");
680     return 0x030a;
681 }
682