1/* 2Copyright (C) 1996-1997 Id Software, Inc. 3 4This program is free software; you can redistribute it and/or 5modify it under the terms of the GNU General Public License 6as published by the Free Software Foundation; either version 2 7of the License, or (at your option) any later version. 8 9This program is distributed in the hope that it will be useful, 10but WITHOUT ANY WARRANTY; without even the implied warranty of 11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 13See the GNU General Public License for more details. 14 15You should have received a copy of the GNU General Public License 16along with this program; if not, write to the Free Software 17Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 19*/ 20#include "quakedef.h" 21#include "winquake.h" 22 23#define iDirectSoundCreate(a,b,c) pDirectSoundCreate(a,b,c) 24 25HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter); 26 27// 64K is > 1 second at 16-bit, 22050 Hz 28#define WAV_BUFFERS 64 29#define WAV_MASK 0x3F 30#define WAV_BUFFER_SIZE 0x0400 31#define SECONDARY_BUFFER_SIZE 0x10000 32 33typedef enum {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat; 34 35static qboolean wavonly; 36static qboolean dsound_init; 37static qboolean wav_init; 38static qboolean snd_firsttime = true, snd_isdirect, snd_iswave; 39static qboolean primary_format_set; 40 41static int sample16; 42static int snd_sent, snd_completed; 43 44 45/* 46 * Global variables. Must be visible to window-procedure function 47 * so it can unlock and free the data block after it has been played. 48 */ 49 50HANDLE hData; 51HPSTR lpData, lpData2; 52 53HGLOBAL hWaveHdr; 54LPWAVEHDR lpWaveHdr; 55 56HWAVEOUT hWaveOut; 57 58WAVEOUTCAPS wavecaps; 59 60DWORD gSndBufSize; 61 62MMTIME mmstarttime; 63 64LPDIRECTSOUND pDS; 65LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf; 66 67HINSTANCE hInstDS; 68 69qboolean SNDDMA_InitDirect (void); 70qboolean SNDDMA_InitWav (void); 71 72 73/* 74================== 75S_BlockSound 76================== 77*/ 78void S_BlockSound (void) 79{ 80 81// DirectSound takes care of blocking itself 82 if (snd_iswave) 83 { 84 snd_blocked++; 85 86 if (snd_blocked == 1) 87 { 88 waveOutReset (hWaveOut); 89 } 90 } 91} 92 93 94/* 95================== 96S_UnblockSound 97================== 98*/ 99void S_UnblockSound (void) 100{ 101 102// DirectSound takes care of blocking itself 103 if (snd_iswave) 104 { 105 snd_blocked--; 106 } 107} 108 109 110/* 111================== 112FreeSound 113================== 114*/ 115void FreeSound (void) 116{ 117 int i; 118 119 if (pDSBuf) 120 { 121 pDSBuf->lpVtbl->Stop(pDSBuf); 122 pDSBuf->lpVtbl->Release(pDSBuf); 123 } 124 125// only release primary buffer if it's not also the mixing buffer we just released 126 if (pDSPBuf && (pDSBuf != pDSPBuf)) 127 { 128 pDSPBuf->lpVtbl->Release(pDSPBuf); 129 } 130 131 if (pDS) 132 { 133 pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_NORMAL); 134 pDS->lpVtbl->Release(pDS); 135 } 136 137 if (hWaveOut) 138 { 139 waveOutReset (hWaveOut); 140 141 if (lpWaveHdr) 142 { 143 for (i=0 ; i< WAV_BUFFERS ; i++) 144 waveOutUnprepareHeader (hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)); 145 } 146 147 waveOutClose (hWaveOut); 148 149 if (hWaveHdr) 150 { 151 GlobalUnlock(hWaveHdr); 152 GlobalFree(hWaveHdr); 153 } 154 155 if (hData) 156 { 157 GlobalUnlock(hData); 158 GlobalFree(hData); 159 } 160 161 } 162 163 pDS = NULL; 164 pDSBuf = NULL; 165 pDSPBuf = NULL; 166 hWaveOut = 0; 167 hData = 0; 168 hWaveHdr = 0; 169 lpData = NULL; 170 lpWaveHdr = NULL; 171 dsound_init = false; 172 wav_init = false; 173} 174 175 176/* 177================== 178SNDDMA_InitDirect 179 180Direct-Sound support 181================== 182*/ 183sndinitstat SNDDMA_InitDirect (void) 184{ 185 DSBUFFERDESC dsbuf; 186 DSBCAPS dsbcaps; 187 DWORD dwSize, dwWrite; 188 DSCAPS dscaps; 189 WAVEFORMATEX format, pformat; 190 HRESULT hresult; 191 int reps; 192 193 memset ((void *)&sn, 0, sizeof (sn)); 194 195 shm = &sn; 196 197 shm->channels = 2; 198 shm->samplebits = 16; 199 shm->speed = 11025; 200 201 memset (&format, 0, sizeof(format)); 202 format.wFormatTag = WAVE_FORMAT_PCM; 203 format.nChannels = shm->channels; 204 format.wBitsPerSample = shm->samplebits; 205 format.nSamplesPerSec = shm->speed; 206 format.nBlockAlign = format.nChannels 207 *format.wBitsPerSample / 8; 208 format.cbSize = 0; 209 format.nAvgBytesPerSec = format.nSamplesPerSec 210 *format.nBlockAlign; 211 212 if (!hInstDS) 213 { 214 hInstDS = LoadLibrary("dsound.dll"); 215 216 if (hInstDS == NULL) 217 { 218 Con_SafePrintf ("Couldn't load dsound.dll\n"); 219 return SIS_FAILURE; 220 } 221 222 pDirectSoundCreate = (void *)GetProcAddress(hInstDS,"DirectSoundCreate"); 223 224 if (!pDirectSoundCreate) 225 { 226 Con_SafePrintf ("Couldn't get DS proc addr\n"); 227 return SIS_FAILURE; 228 } 229 } 230 231 while ((hresult = iDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK) 232 { 233 if (hresult != DSERR_ALLOCATED) 234 { 235 Con_SafePrintf ("DirectSound create failed\n"); 236 return SIS_FAILURE; 237 } 238 239 if (MessageBox (NULL, 240 "The sound hardware is in use by another app.\n\n" 241 "Select Retry to try to start sound again or Cancel to run Quake with no sound.", 242 "Sound not available", 243 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY) 244 { 245 Con_SafePrintf ("DirectSoundCreate failure\n" 246 " hardware already in use\n"); 247 return SIS_NOTAVAIL; 248 } 249 } 250 251 dscaps.dwSize = sizeof(dscaps); 252 253 if (DS_OK != pDS->lpVtbl->GetCaps (pDS, &dscaps)) 254 { 255 Con_SafePrintf ("Couldn't get DS caps\n"); 256 } 257 258 if (dscaps.dwFlags & DSCAPS_EMULDRIVER) 259 { 260 Con_SafePrintf ("No DirectSound driver installed\n"); 261 FreeSound (); 262 return SIS_FAILURE; 263 } 264 265 if (DS_OK != pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_EXCLUSIVE)) 266 { 267 Con_SafePrintf ("Set coop level failed\n"); 268 FreeSound (); 269 return SIS_FAILURE; 270 } 271 272// get access to the primary buffer, if possible, so we can set the 273// sound hardware format 274 memset (&dsbuf, 0, sizeof(dsbuf)); 275 dsbuf.dwSize = sizeof(DSBUFFERDESC); 276 dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER; 277 dsbuf.dwBufferBytes = 0; 278 dsbuf.lpwfxFormat = NULL; 279 280 memset(&dsbcaps, 0, sizeof(dsbcaps)); 281 dsbcaps.dwSize = sizeof(dsbcaps); 282 primary_format_set = false; 283 284 if (!COM_CheckParm ("-snoforceformat")) 285 { 286 if (DS_OK == pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL)) 287 { 288 pformat = format; 289 290 if (DS_OK != pDSPBuf->lpVtbl->SetFormat (pDSPBuf, &pformat)) 291 { 292 if (snd_firsttime) 293 Con_SafePrintf ("Set primary sound buffer format: no\n"); 294 } 295 else 296 { 297 if (snd_firsttime) 298 Con_SafePrintf ("Set primary sound buffer format: yes\n"); 299 300 primary_format_set = true; 301 } 302 } 303 } 304 305 if (!primary_format_set || !COM_CheckParm ("-primarysound")) 306 { 307 // create the secondary buffer we'll actually work with 308 memset (&dsbuf, 0, sizeof(dsbuf)); 309 dsbuf.dwSize = sizeof(DSBUFFERDESC); 310 dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE; 311 dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE; 312 dsbuf.lpwfxFormat = &format; 313 314 memset(&dsbcaps, 0, sizeof(dsbcaps)); 315 dsbcaps.dwSize = sizeof(dsbcaps); 316 317 if (DS_OK != pDS->lpVtbl->CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL)) 318 { 319 Con_SafePrintf ("DS:CreateSoundBuffer Failed"); 320 FreeSound (); 321 return SIS_FAILURE; 322 } 323 324 shm->channels = format.nChannels; 325 shm->samplebits = format.wBitsPerSample; 326 shm->speed = format.nSamplesPerSec; 327 328 if (DS_OK != pDSBuf->lpVtbl->GetCaps (pDSBuf, &dsbcaps)) 329 { 330 Con_SafePrintf ("DS:GetCaps failed\n"); 331 FreeSound (); 332 return SIS_FAILURE; 333 } 334 335 if (snd_firsttime) 336 Con_SafePrintf ("Using secondary sound buffer\n"); 337 } 338 else 339 { 340 if (DS_OK != pDS->lpVtbl->SetCooperativeLevel (pDS, mainwindow, DSSCL_WRITEPRIMARY)) 341 { 342 Con_SafePrintf ("Set coop level failed\n"); 343 FreeSound (); 344 return SIS_FAILURE; 345 } 346 347 if (DS_OK != pDSPBuf->lpVtbl->GetCaps (pDSPBuf, &dsbcaps)) 348 { 349 Con_Printf ("DS:GetCaps failed\n"); 350 return SIS_FAILURE; 351 } 352 353 pDSBuf = pDSPBuf; 354 Con_SafePrintf ("Using primary sound buffer\n"); 355 } 356 357 // Make sure mixer is active 358 pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); 359 360 if (snd_firsttime) 361 Con_SafePrintf(" %d channel(s)\n" 362 " %d bits/sample\n" 363 " %d bytes/sec\n", 364 shm->channels, shm->samplebits, shm->speed); 365 366 gSndBufSize = dsbcaps.dwBufferBytes; 367 368// initialize the buffer 369 reps = 0; 370 371 while ((hresult = pDSBuf->lpVtbl->Lock(pDSBuf, 0, gSndBufSize, &lpData, &dwSize, NULL, NULL, 0)) != DS_OK) 372 { 373 if (hresult != DSERR_BUFFERLOST) 374 { 375 Con_SafePrintf ("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n"); 376 FreeSound (); 377 return SIS_FAILURE; 378 } 379 380 if (++reps > 10000) 381 { 382 Con_SafePrintf ("SNDDMA_InitDirect: DS: couldn't restore buffer\n"); 383 FreeSound (); 384 return SIS_FAILURE; 385 } 386 387 } 388 389 memset(lpData, 0, dwSize); 390// lpData[4] = lpData[5] = 0x7f; // force a pop for debugging 391 392 pDSBuf->lpVtbl->Unlock(pDSBuf, lpData, dwSize, NULL, 0); 393 394 /* we don't want anyone to access the buffer directly w/o locking it first. */ 395 lpData = NULL; 396 397 pDSBuf->lpVtbl->Stop(pDSBuf); 398 pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &mmstarttime.u.sample, &dwWrite); 399 pDSBuf->lpVtbl->Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); 400 401 shm->soundalive = true; 402 shm->splitbuffer = false; 403 shm->samples = gSndBufSize/(shm->samplebits/8); 404 shm->samplepos = 0; 405 shm->submission_chunk = 1; 406 shm->buffer = (unsigned char *) lpData; 407 sample16 = (shm->samplebits/8) - 1; 408 409 dsound_init = true; 410 411 return SIS_SUCCESS; 412} 413 414 415/* 416================== 417SNDDM_InitWav 418 419Crappy windows multimedia base 420================== 421*/ 422qboolean SNDDMA_InitWav (void) 423{ 424 WAVEFORMATEX format; 425 int i; 426 HRESULT hr; 427 428 snd_sent = 0; 429 snd_completed = 0; 430 431 shm = &sn; 432 433 shm->channels = 2; 434 shm->samplebits = 16; 435 shm->speed = 11025; 436 437 memset (&format, 0, sizeof(format)); 438 format.wFormatTag = WAVE_FORMAT_PCM; 439 format.nChannels = shm->channels; 440 format.wBitsPerSample = shm->samplebits; 441 format.nSamplesPerSec = shm->speed; 442 format.nBlockAlign = format.nChannels 443 *format.wBitsPerSample / 8; 444 format.cbSize = 0; 445 format.nAvgBytesPerSec = format.nSamplesPerSec 446 *format.nBlockAlign; 447 448 /* Open a waveform device for output using window callback. */ 449 while ((hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER, 450 &format, 451 0, 0L, CALLBACK_NULL)) != MMSYSERR_NOERROR) 452 { 453 if (hr != MMSYSERR_ALLOCATED) 454 { 455 Con_SafePrintf ("waveOutOpen failed\n"); 456 return false; 457 } 458 459 if (MessageBox (NULL, 460 "The sound hardware is in use by another app.\n\n" 461 "Select Retry to try to start sound again or Cancel to run Quake with no sound.", 462 "Sound not available", 463 MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY) 464 { 465 Con_SafePrintf ("waveOutOpen failure;\n" 466 " hardware already in use\n"); 467 return false; 468 } 469 } 470 471 /* 472 * Allocate and lock memory for the waveform data. The memory 473 * for waveform data must be globally allocated with 474 * GMEM_MOVEABLE and GMEM_SHARE flags. 475 476 */ 477 gSndBufSize = WAV_BUFFERS*WAV_BUFFER_SIZE; 478 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize); 479 if (!hData) 480 { 481 Con_SafePrintf ("Sound: Out of memory.\n"); 482 FreeSound (); 483 return false; 484 } 485 lpData = GlobalLock(hData); 486 if (!lpData) 487 { 488 Con_SafePrintf ("Sound: Failed to lock.\n"); 489 FreeSound (); 490 return false; 491 } 492 memset (lpData, 0, gSndBufSize); 493 494 /* 495 * Allocate and lock memory for the header. This memory must 496 * also be globally allocated with GMEM_MOVEABLE and 497 * GMEM_SHARE flags. 498 */ 499 hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, 500 (DWORD) sizeof(WAVEHDR) * WAV_BUFFERS); 501 502 if (hWaveHdr == NULL) 503 { 504 Con_SafePrintf ("Sound: Failed to Alloc header.\n"); 505 FreeSound (); 506 return false; 507 } 508 509 lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr); 510 511 if (lpWaveHdr == NULL) 512 { 513 Con_SafePrintf ("Sound: Failed to lock header.\n"); 514 FreeSound (); 515 return false; 516 } 517 518 memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS); 519 520 /* After allocation, set up and prepare headers. */ 521 for (i=0 ; i<WAV_BUFFERS ; i++) 522 { 523 lpWaveHdr[i].dwBufferLength = WAV_BUFFER_SIZE; 524 lpWaveHdr[i].lpData = lpData + i*WAV_BUFFER_SIZE; 525 526 if (waveOutPrepareHeader(hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)) != 527 MMSYSERR_NOERROR) 528 { 529 Con_SafePrintf ("Sound: failed to prepare wave headers\n"); 530 FreeSound (); 531 return false; 532 } 533 } 534 535 shm->soundalive = true; 536 shm->splitbuffer = false; 537 shm->samples = gSndBufSize/(shm->samplebits/8); 538 shm->samplepos = 0; 539 shm->submission_chunk = 1; 540 shm->buffer = (unsigned char *) lpData; 541 sample16 = (shm->samplebits/8) - 1; 542 543 wav_init = true; 544 545 return true; 546} 547 548/* 549================== 550SNDDMA_Init 551 552Try to find a sound device to mix for. 553Returns false if nothing is found. 554================== 555*/ 556 557int SNDDMA_Init(void) 558{ 559 sndinitstat stat; 560 561 if (COM_CheckParm ("-wavonly")) 562 wavonly = true; 563 564 dsound_init = wav_init = 0; 565 566 stat = SIS_FAILURE; // assume DirectSound won't initialize 567 568 /* Init DirectSound */ 569 if (!wavonly) 570 { 571 if (snd_firsttime || snd_isdirect) 572 { 573 stat = SNDDMA_InitDirect ();; 574 575 if (stat == SIS_SUCCESS) 576 { 577 snd_isdirect = true; 578 579 if (snd_firsttime) 580 Con_SafePrintf ("DirectSound initialized\n"); 581 } 582 else 583 { 584 snd_isdirect = false; 585 Con_SafePrintf ("DirectSound failed to init\n"); 586 } 587 } 588 } 589 590// if DirectSound didn't succeed in initializing, try to initialize 591// waveOut sound, unless DirectSound failed because the hardware is 592// already allocated (in which case the user has already chosen not 593// to have sound) 594 if (!dsound_init && (stat != SIS_NOTAVAIL)) 595 { 596 if (snd_firsttime || snd_iswave) 597 { 598 599 snd_iswave = SNDDMA_InitWav (); 600 601 if (snd_iswave) 602 { 603 if (snd_firsttime) 604 Con_SafePrintf ("Wave sound initialized\n"); 605 } 606 else 607 { 608 Con_SafePrintf ("Wave sound failed to init\n"); 609 } 610 } 611 } 612 613 snd_firsttime = false; 614 615 if (!dsound_init && !wav_init) 616 { 617 if (snd_firsttime) 618 Con_SafePrintf ("No sound device initialized\n"); 619 620 return 0; 621 } 622 623 return 1; 624} 625 626/* 627============== 628SNDDMA_GetDMAPos 629 630return the current sample position (in mono samples read) 631inside the recirculating dma buffer, so the mixing code will know 632how many sample are required to fill it up. 633=============== 634*/ 635int SNDDMA_GetDMAPos(void) 636{ 637 MMTIME mmtime; 638 int s; 639 DWORD dwWrite; 640 641 if (dsound_init) 642 { 643 mmtime.wType = TIME_SAMPLES; 644 pDSBuf->lpVtbl->GetCurrentPosition(pDSBuf, &mmtime.u.sample, &dwWrite); 645 s = mmtime.u.sample - mmstarttime.u.sample; 646 } 647 else if (wav_init) 648 { 649 s = snd_sent * WAV_BUFFER_SIZE; 650 } 651 652 653 s >>= sample16; 654 655 s &= (shm->samples-1); 656 657 return s; 658} 659 660/* 661============== 662SNDDMA_Submit 663 664Send sound to device if buffer isn't really the dma buffer 665=============== 666*/ 667void SNDDMA_Submit(void) 668{ 669 LPWAVEHDR h; 670 int wResult; 671 672 if (!wav_init) 673 return; 674 675 // 676 // find which sound blocks have completed 677 // 678 while (1) 679 { 680 if ( snd_completed == snd_sent ) 681 { 682 Con_DPrintf ("Sound overrun\n"); 683 break; 684 } 685 686 if ( ! (lpWaveHdr[ snd_completed & WAV_MASK].dwFlags & WHDR_DONE) ) 687 { 688 break; 689 } 690 691 snd_completed++; // this buffer has been played 692 } 693 694 // 695 // submit two new sound blocks 696 // 697 while (((snd_sent - snd_completed) >> sample16) < 4) 698 { 699 h = lpWaveHdr + ( snd_sent&WAV_MASK ); 700 701 snd_sent++; 702 /* 703 * Now the data block can be sent to the output device. The 704 * waveOutWrite function returns immediately and waveform 705 * data is sent to the output device in the background. 706 */ 707 wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR)); 708 709 if (wResult != MMSYSERR_NOERROR) 710 { 711 Con_SafePrintf ("Failed to write block to device\n"); 712 FreeSound (); 713 return; 714 } 715 } 716} 717 718/* 719============== 720SNDDMA_Shutdown 721 722Reset the sound device for exiting 723=============== 724*/ 725void SNDDMA_Shutdown(void) 726{ 727 FreeSound (); 728} 729 730