DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

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 }