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