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