XA2_SoundHardware.cpp (18053B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code 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 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. 23 24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. 25 26 =========================================================================== 27 */ 28 #pragma hdrstop 29 #include "../../idlib/precompiled.h" 30 #include "../snd_local.h" 31 #include "../../../doomclassic/doom/i_sound.h" 32 33 idCVar s_showLevelMeter( "s_showLevelMeter", "0", CVAR_BOOL|CVAR_ARCHIVE, "Show VU meter" ); 34 idCVar s_meterTopTime( "s_meterTopTime", "1000", CVAR_INTEGER|CVAR_ARCHIVE, "How long (in milliseconds) peaks are displayed on the VU meter" ); 35 idCVar s_meterPosition( "s_meterPosition", "100 100 20 200", CVAR_ARCHIVE, "VU meter location (x y w h)" ); 36 idCVar s_device( "s_device", "-1", CVAR_INTEGER|CVAR_ARCHIVE, "Which audio device to use (listDevices to list, -1 for default)" ); 37 idCVar s_showPerfData( "s_showPerfData", "0", CVAR_BOOL, "Show XAudio2 Performance data" ); 38 extern idCVar s_volume_dB; 39 40 /* 41 ======================== 42 idSoundHardware_XAudio2::idSoundHardware_XAudio2 43 ======================== 44 */ 45 idSoundHardware_XAudio2::idSoundHardware_XAudio2() { 46 pXAudio2 = NULL; 47 pMasterVoice = NULL; 48 pSubmixVoice = NULL; 49 50 vuMeterRMS = NULL; 51 vuMeterPeak = NULL; 52 53 outputChannels = 0; 54 channelMask = 0; 55 56 voices.SetNum( 0 ); 57 zombieVoices.SetNum( 0 ); 58 freeVoices.SetNum( 0 ); 59 60 lastResetTime = 0; 61 } 62 63 void listDevices_f( const idCmdArgs & args ) { 64 65 IXAudio2 * pXAudio2 = soundSystemLocal.hardware.GetIXAudio2(); 66 67 if ( pXAudio2 == NULL ) { 68 idLib::Warning( "No xaudio object" ); 69 return; 70 } 71 72 UINT32 deviceCount = 0; 73 if ( pXAudio2->GetDeviceCount( &deviceCount ) != S_OK || deviceCount == 0 ) { 74 idLib::Warning( "No audio devices found" ); 75 return; 76 } 77 78 for ( unsigned int i = 0; i < deviceCount; i++ ) { 79 XAUDIO2_DEVICE_DETAILS deviceDetails; 80 if ( pXAudio2->GetDeviceDetails( i, &deviceDetails ) != S_OK ) { 81 continue; 82 } 83 idStaticList< const char *, 5 > roles; 84 if ( deviceDetails.Role & DefaultConsoleDevice ) { 85 roles.Append( "Console Device" ); 86 } 87 if ( deviceDetails.Role & DefaultMultimediaDevice ) { 88 roles.Append( "Multimedia Device" ); 89 } 90 if ( deviceDetails.Role & DefaultCommunicationsDevice ) { 91 roles.Append( "Communications Device" ); 92 } 93 if ( deviceDetails.Role & DefaultGameDevice ) { 94 roles.Append( "Game Device" ); 95 } 96 idStaticList< const char *, 11 > channelNames; 97 if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_LEFT ) { 98 channelNames.Append( "Front Left" ); 99 } 100 if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_RIGHT ) { 101 channelNames.Append( "Front Right" ); 102 } 103 if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_CENTER ) { 104 channelNames.Append( "Front Center" ); 105 } 106 if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_LOW_FREQUENCY ) { 107 channelNames.Append( "Low Frequency" ); 108 } 109 if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_BACK_LEFT ) { 110 channelNames.Append( "Back Left" ); 111 } 112 if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_BACK_RIGHT ) { 113 channelNames.Append( "Back Right" ); 114 } 115 if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_LEFT_OF_CENTER ) { 116 channelNames.Append( "Front Left of Center" ); 117 } 118 if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_RIGHT_OF_CENTER ) { 119 channelNames.Append( "Front Right of Center" ); 120 } 121 if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_BACK_CENTER ) { 122 channelNames.Append( "Back Center" ); 123 } 124 if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_SIDE_LEFT ) { 125 channelNames.Append( "Side Left" ); 126 } 127 if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_SIDE_RIGHT ) { 128 channelNames.Append( "Side Right" ); 129 } 130 char mbcsDisplayName[ 256 ]; 131 wcstombs( mbcsDisplayName, deviceDetails.DisplayName, sizeof( mbcsDisplayName ) ); 132 idLib::Printf( "%3d: %s\n", i, mbcsDisplayName ); 133 idLib::Printf( " %d channels, %d Hz\n", deviceDetails.OutputFormat.Format.nChannels, deviceDetails.OutputFormat.Format.nSamplesPerSec ); 134 if ( channelNames.Num() != deviceDetails.OutputFormat.Format.nChannels ) { 135 idLib::Printf( S_COLOR_YELLOW "WARNING: " S_COLOR_RED "Mismatch between # of channels and channel mask\n" ); 136 } 137 if ( channelNames.Num() == 1 ) { 138 idLib::Printf( " %s\n", channelNames[0] ); 139 } else if ( channelNames.Num() == 2 ) { 140 idLib::Printf( " %s and %s\n", channelNames[0], channelNames[1] ); 141 } else if ( channelNames.Num() > 2 ) { 142 idLib::Printf( " %s", channelNames[0] ); 143 for ( int i = 1; i < channelNames.Num() - 1; i++ ) { 144 idLib::Printf( ", %s", channelNames[i] ); 145 } 146 idLib::Printf( ", and %s\n", channelNames[channelNames.Num() - 1] ); 147 } 148 if ( roles.Num() == 1 ) { 149 idLib::Printf( " Default %s\n", roles[0] ); 150 } else if ( roles.Num() == 2 ) { 151 idLib::Printf( " Default %s and %s\n", roles[0], roles[1] ); 152 } else if ( roles.Num() > 2 ) { 153 idLib::Printf( " Default %s", roles[0] ); 154 for ( int i = 1; i < roles.Num() - 1; i++ ) { 155 idLib::Printf( ", %s", roles[i] ); 156 } 157 idLib::Printf( ", and %s\n", roles[roles.Num() - 1] ); 158 } 159 } 160 } 161 162 /* 163 ======================== 164 idSoundHardware_XAudio2::Init 165 ======================== 166 */ 167 void idSoundHardware_XAudio2::Init() { 168 169 cmdSystem->AddCommand( "listDevices", listDevices_f, 0, "Lists the connected sound devices", NULL ); 170 171 DWORD xAudioCreateFlags = 0; 172 #ifdef _DEBUG 173 xAudioCreateFlags |= XAUDIO2_DEBUG_ENGINE; 174 #endif 175 176 XAUDIO2_PROCESSOR xAudioProcessor = XAUDIO2_DEFAULT_PROCESSOR; 177 178 if ( FAILED( XAudio2Create( &pXAudio2, xAudioCreateFlags, xAudioProcessor ) ) ) { 179 if ( xAudioCreateFlags & XAUDIO2_DEBUG_ENGINE ) { 180 // in case the debug engine isn't installed 181 xAudioCreateFlags &= ~XAUDIO2_DEBUG_ENGINE; 182 if ( FAILED( XAudio2Create( &pXAudio2, xAudioCreateFlags, xAudioProcessor ) ) ) { 183 idLib::FatalError( "Failed to create XAudio2 engine. Try installing the latest DirectX." ); 184 return; 185 } 186 } else { 187 idLib::FatalError( "Failed to create XAudio2 engine. Try installing the latest DirectX." ); 188 return; 189 } 190 } 191 #ifdef _DEBUG 192 XAUDIO2_DEBUG_CONFIGURATION debugConfiguration = { 0 }; 193 debugConfiguration.TraceMask = XAUDIO2_LOG_WARNINGS; 194 debugConfiguration.BreakMask = XAUDIO2_LOG_ERRORS; 195 pXAudio2->SetDebugConfiguration( &debugConfiguration ); 196 #endif 197 198 // Register the sound engine callback 199 pXAudio2->RegisterForCallbacks( &soundEngineCallback ); 200 soundEngineCallback.hardware = this; 201 202 UINT32 deviceCount = 0; 203 if ( pXAudio2->GetDeviceCount( &deviceCount ) != S_OK || deviceCount == 0 ) { 204 idLib::Warning( "No audio devices found" ); 205 pXAudio2->Release(); 206 pXAudio2 = NULL; 207 return; 208 } 209 210 idCmdArgs args; 211 listDevices_f( args ); 212 213 int preferredDevice = s_device.GetInteger(); 214 if ( preferredDevice < 0 || preferredDevice >= (int)deviceCount ) { 215 int preferredChannels = 0; 216 for ( unsigned int i = 0; i < deviceCount; i++ ) { 217 XAUDIO2_DEVICE_DETAILS deviceDetails; 218 if ( pXAudio2->GetDeviceDetails( i, &deviceDetails ) != S_OK ) { 219 continue; 220 } 221 222 if ( deviceDetails.Role & DefaultGameDevice ) { 223 // if we find a device the user marked as their preferred 'game' device, then always use that 224 preferredDevice = i; 225 preferredChannels = deviceDetails.OutputFormat.Format.nChannels; 226 break; 227 } 228 229 if ( deviceDetails.OutputFormat.Format.nChannels > preferredChannels ) { 230 preferredDevice = i; 231 preferredChannels = deviceDetails.OutputFormat.Format.nChannels; 232 } 233 } 234 } 235 236 idLib::Printf( "Using device %d\n", preferredDevice ); 237 238 XAUDIO2_DEVICE_DETAILS deviceDetails; 239 if ( pXAudio2->GetDeviceDetails( preferredDevice, &deviceDetails ) != S_OK ) { 240 // One way this could happen is if a device is removed between the loop and this line of code 241 // Highly unlikely but possible 242 idLib::Warning( "Failed to get device details" ); 243 pXAudio2->Release(); 244 pXAudio2 = NULL; 245 return; 246 } 247 248 DWORD outputSampleRate = 44100; // Max( (DWORD)XAUDIO2FX_REVERB_MIN_FRAMERATE, Min( (DWORD)XAUDIO2FX_REVERB_MAX_FRAMERATE, deviceDetails.OutputFormat.Format.nSamplesPerSec ) ); 249 250 if ( FAILED( pXAudio2->CreateMasteringVoice( &pMasterVoice, XAUDIO2_DEFAULT_CHANNELS, outputSampleRate, 0, preferredDevice, NULL ) ) ) { 251 idLib::Warning( "Failed to create master voice" ); 252 pXAudio2->Release(); 253 pXAudio2 = NULL; 254 return; 255 } 256 pMasterVoice->SetVolume( DBtoLinear( s_volume_dB.GetFloat() ) ); 257 258 outputChannels = deviceDetails.OutputFormat.Format.nChannels; 259 channelMask = deviceDetails.OutputFormat.dwChannelMask; 260 261 idSoundVoice::InitSurround( outputChannels, channelMask ); 262 263 // --------------------- 264 // Initialize the Doom classic sound system. 265 // --------------------- 266 I_InitSoundHardware( outputChannels, channelMask ); 267 268 // --------------------- 269 // Create VU Meter Effect 270 // --------------------- 271 IUnknown * vuMeter = NULL; 272 XAudio2CreateVolumeMeter( &vuMeter, 0 ); 273 274 XAUDIO2_EFFECT_DESCRIPTOR descriptor; 275 descriptor.InitialState = true; 276 descriptor.OutputChannels = outputChannels; 277 descriptor.pEffect = vuMeter; 278 279 XAUDIO2_EFFECT_CHAIN chain; 280 chain.EffectCount = 1; 281 chain.pEffectDescriptors = &descriptor; 282 283 pMasterVoice->SetEffectChain( &chain ); 284 285 vuMeter->Release(); 286 287 // --------------------- 288 // Create VU Meter Graph 289 // --------------------- 290 291 vuMeterRMS = console->CreateGraph( outputChannels ); 292 vuMeterPeak = console->CreateGraph( outputChannels ); 293 vuMeterRMS->Enable( false ); 294 vuMeterPeak->Enable( false ); 295 296 memset( vuMeterPeakTimes, 0, sizeof( vuMeterPeakTimes ) ); 297 298 vuMeterPeak->SetFillMode( idDebugGraph::GRAPH_LINE ); 299 vuMeterPeak->SetBackgroundColor( idVec4( 0.0f, 0.0f, 0.0f, 0.0f ) ); 300 301 vuMeterRMS->AddGridLine( 0.500f, idVec4( 0.5f, 0.5f, 0.5f, 1.0f ) ); 302 vuMeterRMS->AddGridLine( 0.250f, idVec4( 0.5f, 0.5f, 0.5f, 1.0f ) ); 303 vuMeterRMS->AddGridLine( 0.125f, idVec4( 0.5f, 0.5f, 0.5f, 1.0f ) ); 304 305 const char * channelNames[] = { "L", "R", "C", "S", "Lb", "Rb", "Lf", "Rf", "Cb", "Ls", "Rs" }; 306 for ( int i = 0, ci = 0; ci < sizeof( channelNames ) / sizeof( channelNames[0] ); ci++ ) { 307 if ( ( channelMask & BIT( ci ) ) == 0 ) { 308 continue; 309 } 310 vuMeterRMS->SetLabel( i, channelNames[ ci ] ); 311 i++; 312 } 313 314 // --------------------- 315 // Create submix buffer 316 // --------------------- 317 if ( FAILED( pXAudio2->CreateSubmixVoice( &pSubmixVoice, 1, outputSampleRate, 0, 0, NULL, NULL ) ) ) { 318 idLib::FatalError( "Failed to create submix voice" ); 319 } 320 321 // XAudio doesn't really impose a maximum number of voices 322 voices.SetNum( voices.Max() ); 323 freeVoices.SetNum( voices.Max() ); 324 zombieVoices.SetNum( 0 ); 325 for ( int i = 0; i < voices.Num(); i++ ) { 326 freeVoices[i] = &voices[i]; 327 } 328 } 329 330 /* 331 ======================== 332 idSoundHardware_XAudio2::Shutdown 333 ======================== 334 */ 335 void idSoundHardware_XAudio2::Shutdown() { 336 for ( int i = 0; i < voices.Num(); i++ ) { 337 voices[ i ].DestroyInternal(); 338 } 339 voices.Clear(); 340 freeVoices.Clear(); 341 zombieVoices.Clear(); 342 343 // --------------------- 344 // Shutdown the Doom classic sound system. 345 // --------------------- 346 I_ShutdownSoundHardware(); 347 348 if ( pXAudio2 != NULL ) { 349 // Unregister the sound engine callback 350 pXAudio2->UnregisterForCallbacks( &soundEngineCallback ); 351 } 352 353 if ( pSubmixVoice != NULL ) { 354 pSubmixVoice->DestroyVoice(); 355 pSubmixVoice = NULL; 356 } 357 if ( pMasterVoice != NULL ) { 358 // release the vu meter effect 359 pMasterVoice->SetEffectChain( NULL ); 360 pMasterVoice->DestroyVoice(); 361 pMasterVoice = NULL; 362 } 363 if ( pXAudio2 != NULL ) { 364 XAUDIO2_PERFORMANCE_DATA perfData; 365 pXAudio2->GetPerformanceData( &perfData ); 366 idLib::Printf( "Final pXAudio2 performanceData: Voices: %d/%d CPU: %.2f%% Mem: %dkb\n", perfData.ActiveSourceVoiceCount, perfData.TotalSourceVoiceCount, perfData.AudioCyclesSinceLastQuery / (float)perfData.TotalCyclesSinceLastQuery, perfData.MemoryUsageInBytes / 1024 ); 367 pXAudio2->Release(); 368 pXAudio2 = NULL; 369 } 370 if ( vuMeterRMS != NULL ) { 371 console->DestroyGraph( vuMeterRMS ); 372 vuMeterRMS = NULL; 373 } 374 if ( vuMeterPeak != NULL ) { 375 console->DestroyGraph( vuMeterPeak ); 376 vuMeterPeak = NULL; 377 } 378 } 379 380 /* 381 ======================== 382 idSoundHardware_XAudio2::AllocateVoice 383 ======================== 384 */ 385 idSoundVoice * idSoundHardware_XAudio2::AllocateVoice( const idSoundSample * leadinSample, const idSoundSample * loopingSample ) { 386 if ( leadinSample == NULL ) { 387 return NULL; 388 } 389 if ( loopingSample != NULL ) { 390 if ( ( leadinSample->format.basic.formatTag != loopingSample->format.basic.formatTag ) || ( leadinSample->format.basic.numChannels != loopingSample->format.basic.numChannels ) ) { 391 idLib::Warning( "Leadin/looping format mismatch: %s & %s", leadinSample->GetName(), loopingSample->GetName() ); 392 loopingSample = NULL; 393 } 394 } 395 396 // Try to find a free voice that matches the format 397 // But fallback to the last free voice if none match the format 398 idSoundVoice * voice = NULL; 399 for ( int i = 0; i < freeVoices.Num(); i++ ) { 400 if ( freeVoices[i]->IsPlaying() ) { 401 continue; 402 } 403 voice = (idSoundVoice *)freeVoices[i]; 404 if ( voice->CompatibleFormat( (idSoundSample_XAudio2*)leadinSample ) ) { 405 break; 406 } 407 } 408 if ( voice != NULL ) { 409 voice->Create( leadinSample, loopingSample ); 410 freeVoices.Remove( voice ); 411 return voice; 412 } 413 414 return NULL; 415 } 416 417 /* 418 ======================== 419 idSoundHardware_XAudio2::FreeVoice 420 ======================== 421 */ 422 void idSoundHardware_XAudio2::FreeVoice( idSoundVoice * voice ) { 423 voice->Stop(); 424 425 // Stop() is asyncronous, so we won't flush bufferes until the 426 // voice on the zombie channel actually returns !IsPlaying() 427 zombieVoices.Append( voice ); 428 } 429 430 /* 431 ======================== 432 idSoundHardware_XAudio2::Update 433 ======================== 434 */ 435 void idSoundHardware_XAudio2::Update() { 436 if ( pXAudio2 == NULL ) { 437 int nowTime = Sys_Milliseconds(); 438 if ( lastResetTime + 1000 < nowTime ) { 439 lastResetTime = nowTime; 440 Init(); 441 } 442 return; 443 } 444 if ( soundSystem->IsMuted() ) { 445 pMasterVoice->SetVolume( 0.0f, OPERATION_SET ); 446 } else { 447 pMasterVoice->SetVolume( DBtoLinear( s_volume_dB.GetFloat() ), OPERATION_SET ); 448 } 449 450 pXAudio2->CommitChanges( XAUDIO2_COMMIT_ALL ); 451 452 // IXAudio2SourceVoice::Stop() has been called for every sound on the 453 // zombie list, but it is documented as asyncronous, so we have to wait 454 // until it actually reports that it is no longer playing. 455 for ( int i = 0; i < zombieVoices.Num(); i++ ) { 456 zombieVoices[i]->FlushSourceBuffers(); 457 if ( !zombieVoices[i]->IsPlaying() ) { 458 freeVoices.Append( zombieVoices[i] ); 459 zombieVoices.RemoveIndexFast( i ); 460 i--; 461 } else { 462 static int playingZombies; 463 playingZombies++; 464 } 465 } 466 467 if ( s_showPerfData.GetBool() ) { 468 XAUDIO2_PERFORMANCE_DATA perfData; 469 pXAudio2->GetPerformanceData( &perfData ); 470 idLib::Printf( "Voices: %d/%d CPU: %.2f%% Mem: %dkb\n", perfData.ActiveSourceVoiceCount, perfData.TotalSourceVoiceCount, perfData.AudioCyclesSinceLastQuery / (float)perfData.TotalCyclesSinceLastQuery, perfData.MemoryUsageInBytes / 1024 ); 471 } 472 473 if ( vuMeterRMS == NULL ) { 474 // Init probably hasn't been called yet 475 return; 476 } 477 478 vuMeterRMS->Enable( s_showLevelMeter.GetBool() ); 479 vuMeterPeak->Enable( s_showLevelMeter.GetBool() ); 480 481 if ( !s_showLevelMeter.GetBool() ) { 482 pMasterVoice->DisableEffect( 0 ); 483 return; 484 } else { 485 pMasterVoice->EnableEffect( 0 ); 486 } 487 488 float peakLevels[ 8 ]; 489 float rmsLevels[ 8 ]; 490 491 XAUDIO2FX_VOLUMEMETER_LEVELS levels; 492 levels.ChannelCount = outputChannels; 493 levels.pPeakLevels = peakLevels; 494 levels.pRMSLevels = rmsLevels; 495 496 if ( levels.ChannelCount > 8 ) { 497 levels.ChannelCount = 8; 498 } 499 500 pMasterVoice->GetEffectParameters( 0, &levels, sizeof( levels ) ); 501 502 int currentTime = Sys_Milliseconds(); 503 for ( int i = 0; i < outputChannels; i++ ) { 504 if ( vuMeterPeakTimes[i] < currentTime ) { 505 vuMeterPeak->SetValue( i, vuMeterPeak->GetValue( i ) * 0.9f, colorRed ); 506 } 507 } 508 509 float width = 20.0f; 510 float height = 200.0f; 511 float left = 100.0f; 512 float top = 100.0f; 513 514 sscanf( s_meterPosition.GetString(), "%f %f %f %f", &left, &top, &width, &height ); 515 516 vuMeterRMS->SetPosition( left, top, width * levels.ChannelCount, height ); 517 vuMeterPeak->SetPosition( left, top, width * levels.ChannelCount, height ); 518 519 for ( uint32 i = 0; i < levels.ChannelCount; i++ ) { 520 vuMeterRMS->SetValue( i, rmsLevels[ i ], idVec4( 0.5f, 1.0f, 0.0f, 1.00f ) ); 521 if ( peakLevels[ i ] >= vuMeterPeak->GetValue( i ) ) { 522 vuMeterPeak->SetValue( i, peakLevels[ i ], colorRed ); 523 vuMeterPeakTimes[i] = currentTime + s_meterTopTime.GetInteger(); 524 } 525 } 526 } 527 528 529 /* 530 ================================================ 531 idSoundEngineCallback 532 ================================================ 533 */ 534 535 /* 536 ======================== 537 idSoundEngineCallback::OnCriticalError 538 ======================== 539 */ 540 void idSoundEngineCallback::OnCriticalError( HRESULT Error ) { 541 soundSystemLocal.SetNeedsRestart(); 542 }