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