DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

XA2_SoundVoice.cpp (14930B)


      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 
     32 idCVar s_skipHardwareSets( "s_skipHardwareSets", "0", CVAR_BOOL, "Do all calculation, but skip XA2 calls" );
     33 idCVar s_debugHardware( "s_debugHardware", "0", CVAR_BOOL, "Print a message any time a hardware voice changes" );
     34 
     35 // The whole system runs at this sample rate
     36 static int SYSTEM_SAMPLE_RATE = 44100;
     37 static float ONE_OVER_SYSTEM_SAMPLE_RATE = 1.0f / SYSTEM_SAMPLE_RATE;
     38 
     39 /*
     40 ========================
     41 idStreamingVoiceContext
     42 ========================
     43 */
     44 class idStreamingVoiceContext : public IXAudio2VoiceCallback {
     45 public:
     46 	STDMETHOD_(void, OnVoiceProcessingPassStart)( UINT32 BytesRequired ) {}
     47 	STDMETHOD_(void, OnVoiceProcessingPassEnd)() {}
     48 	STDMETHOD_(void, OnStreamEnd)() {}
     49 	STDMETHOD_(void, OnBufferStart)( void * pContext ) {
     50 		idSoundSystemLocal::bufferContext_t * bufferContext = (idSoundSystemLocal::bufferContext_t *) pContext;
     51 		bufferContext->voice->OnBufferStart( bufferContext->sample, bufferContext->bufferNumber );
     52 	}
     53 	STDMETHOD_(void, OnLoopEnd)( void * ) {}
     54 	STDMETHOD_(void, OnVoiceError)( void *, HRESULT hr ) { idLib::Warning( "OnVoiceError( %d )", hr ); }
     55 	STDMETHOD_(void, OnBufferEnd)( void* pContext ) {
     56 		idSoundSystemLocal::bufferContext_t * bufferContext = (idSoundSystemLocal::bufferContext_t *) pContext;
     57 		soundSystemLocal.ReleaseStreamBufferContext( bufferContext );
     58 	}
     59 } streamContext;
     60 
     61 /*
     62 ========================
     63 idSoundVoice_XAudio2::idSoundVoice_XAudio2
     64 ========================
     65 */
     66 idSoundVoice_XAudio2::idSoundVoice_XAudio2()
     67 :	pSourceVoice( NULL ),
     68 	leadinSample( NULL ),
     69 	loopingSample( NULL ),
     70 	formatTag( 0 ),
     71 	numChannels( 0 ),
     72 	sampleRate( 0 ),
     73 	paused( true ),
     74 	hasVUMeter( false ) {
     75 
     76 }
     77 
     78 /*
     79 ========================
     80 idSoundVoice_XAudio2::~idSoundVoice_XAudio2
     81 ========================
     82 */
     83 idSoundVoice_XAudio2::~idSoundVoice_XAudio2() {
     84 	DestroyInternal();
     85 }
     86 
     87 /*
     88 ========================
     89 idSoundVoice_XAudio2::CompatibleFormat
     90 ========================
     91 */
     92 bool idSoundVoice_XAudio2::CompatibleFormat( idSoundSample_XAudio2 * s ) {
     93 	if ( pSourceVoice == NULL ) {
     94 		// If this voice has never been allocated, then it's compatible with everything
     95 		return true;
     96 	}
     97 	return false;
     98 }
     99 
    100 /*
    101 ========================
    102 idSoundVoice_XAudio2::Create
    103 ========================
    104 */
    105 void idSoundVoice_XAudio2::Create( const idSoundSample * leadinSample_, const idSoundSample * loopingSample_ ) {
    106 	if ( IsPlaying() ) {
    107 		// This should never hit
    108 		Stop();
    109 		return;
    110 	}
    111 	leadinSample = (idSoundSample_XAudio2 *)leadinSample_;
    112 	loopingSample = (idSoundSample_XAudio2 *)loopingSample_;
    113 
    114 	if ( pSourceVoice != NULL && CompatibleFormat( leadinSample ) ) {
    115 		sampleRate = leadinSample->format.basic.samplesPerSec;
    116 	} else {
    117 		DestroyInternal();
    118 		formatTag = leadinSample->format.basic.formatTag;
    119 		numChannels = leadinSample->format.basic.numChannels;
    120 		sampleRate = leadinSample->format.basic.samplesPerSec;
    121 
    122 		soundSystemLocal.hardware.pXAudio2->CreateSourceVoice( &pSourceVoice, (const WAVEFORMATEX *)&leadinSample->format, XAUDIO2_VOICE_USEFILTER, 4.0f, &streamContext );
    123 		if ( pSourceVoice == NULL ) {
    124 			// If this hits, then we are most likely passing an invalid sample format, which should have been caught by the loader (and the sample defaulted)
    125 			return;
    126 		}
    127 		if ( s_debugHardware.GetBool() ) {
    128 			if ( loopingSample == NULL || loopingSample == leadinSample ) {
    129 				idLib::Printf( "%dms: %p created for %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>" );
    130 			} else {
    131 				idLib::Printf( "%dms: %p created for %s and %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>", loopingSample ? loopingSample->GetName() : "<null>" );
    132 			}
    133 		}
    134 	}
    135 	sourceVoiceRate = sampleRate;
    136 	pSourceVoice->SetSourceSampleRate( sampleRate );
    137 	pSourceVoice->SetVolume( 0.0f );
    138 }
    139 
    140 /*
    141 ========================
    142 idSoundVoice_XAudio2::DestroyInternal
    143 ========================
    144 */
    145 void idSoundVoice_XAudio2::DestroyInternal() {
    146 	if ( pSourceVoice != NULL ) {
    147 		if ( s_debugHardware.GetBool() ) {
    148 			idLib::Printf( "%dms: %p destroyed\n", Sys_Milliseconds(), pSourceVoice );
    149 		}
    150 		pSourceVoice->DestroyVoice();
    151 		pSourceVoice = NULL;
    152 		hasVUMeter = false;
    153 	}
    154 }
    155 
    156 /*
    157 ========================
    158 idSoundVoice_XAudio2::Start
    159 ========================
    160 */
    161 void idSoundVoice_XAudio2::Start( int offsetMS, int ssFlags ) {
    162 
    163 	if ( s_debugHardware.GetBool() ) {
    164 		idLib::Printf( "%dms: %p starting %s @ %dms\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>", offsetMS );
    165 	}
    166 
    167 	if ( !leadinSample ) {
    168 		return;
    169 	}
    170 	if ( !pSourceVoice ) {
    171 		return;
    172 	}
    173 
    174 	if ( leadinSample->IsDefault() ) {
    175 		idLib::Warning( "Starting defaulted sound sample %s", leadinSample->GetName() );
    176 	}
    177 
    178 	bool flicker = ( ssFlags & SSF_NO_FLICKER ) == 0;
    179 
    180 	if ( flicker != hasVUMeter ) {
    181 		hasVUMeter = flicker;
    182 
    183 		if ( flicker ) {
    184 			IUnknown * vuMeter = NULL;
    185 			if ( XAudio2CreateVolumeMeter( &vuMeter, 0 ) == S_OK ) {
    186 
    187 				XAUDIO2_EFFECT_DESCRIPTOR descriptor;
    188 				descriptor.InitialState = true;
    189 				descriptor.OutputChannels = leadinSample->NumChannels();
    190 				descriptor.pEffect = vuMeter;
    191 
    192 				XAUDIO2_EFFECT_CHAIN chain;
    193 				chain.EffectCount = 1;
    194 				chain.pEffectDescriptors = &descriptor;
    195 
    196 				pSourceVoice->SetEffectChain( &chain );
    197 
    198 				vuMeter->Release();
    199 			}
    200 		} else {
    201 			pSourceVoice->SetEffectChain( NULL );
    202 		}
    203 	}
    204 
    205 	assert( offsetMS >= 0 );
    206 	int offsetSamples = MsecToSamples( offsetMS, leadinSample->SampleRate() );
    207 	if ( loopingSample == NULL && offsetSamples >= leadinSample->playLength ) {
    208 		return;
    209 	}
    210 
    211 	RestartAt( offsetSamples );
    212 	Update();
    213 	UnPause();
    214 }
    215 
    216 /*
    217 ========================
    218 idSoundVoice_XAudio2::RestartAt
    219 ========================
    220 */
    221 int idSoundVoice_XAudio2::RestartAt( int offsetSamples ) {
    222 	offsetSamples &= ~127;
    223 
    224 	idSoundSample_XAudio2 * sample = leadinSample;
    225 	if ( offsetSamples >= leadinSample->playLength ) {
    226 		if ( loopingSample != NULL ) {
    227 			offsetSamples %= loopingSample->playLength;
    228 			sample = loopingSample;
    229 		} else {
    230 			return 0;
    231 		}
    232 	}
    233 
    234 	int previousNumSamples = 0;
    235 	for ( int i = 0; i < sample->buffers.Num(); i++ ) {
    236 		if ( sample->buffers[i].numSamples > sample->playBegin + offsetSamples ) {
    237 			return SubmitBuffer( sample, i, sample->playBegin + offsetSamples - previousNumSamples );
    238 		}
    239 		previousNumSamples = sample->buffers[i].numSamples;
    240 	}
    241 
    242 	return 0;
    243 }
    244 
    245 /*
    246 ========================
    247 idSoundVoice_XAudio2::SubmitBuffer
    248 ======================== 
    249 */
    250 int idSoundVoice_XAudio2::SubmitBuffer( idSoundSample_XAudio2 * sample, int bufferNumber, int offset ) {
    251 
    252 	if ( sample == NULL || ( bufferNumber < 0 ) || ( bufferNumber >= sample->buffers.Num() ) ) {
    253 		return 0;
    254 	}
    255 	idSoundSystemLocal::bufferContext_t * bufferContext = soundSystemLocal.ObtainStreamBufferContext();
    256 	if ( bufferContext == NULL ) {
    257 		idLib::Warning( "No free buffer contexts!" );
    258 		return 0;
    259 	}
    260 
    261 	bufferContext->voice = this;
    262 	bufferContext->sample = sample;
    263 	bufferContext->bufferNumber = bufferNumber;
    264 
    265 	XAUDIO2_BUFFER buffer = { 0 };
    266 	if ( offset > 0 ) {
    267 		int previousNumSamples = 0;
    268 		if ( bufferNumber > 0 ) {
    269 			previousNumSamples = sample->buffers[bufferNumber-1].numSamples;
    270 		}
    271 		buffer.PlayBegin = offset;
    272 		buffer.PlayLength = sample->buffers[bufferNumber].numSamples - previousNumSamples - offset;
    273 	}
    274 	buffer.AudioBytes = sample->buffers[bufferNumber].bufferSize;
    275 	buffer.pAudioData = (BYTE *)sample->buffers[bufferNumber].buffer;
    276 	buffer.pContext = bufferContext;
    277 	if ( ( loopingSample == NULL ) && ( bufferNumber == sample->buffers.Num() - 1 ) ) {
    278 		buffer.Flags = XAUDIO2_END_OF_STREAM;
    279 	}
    280 	pSourceVoice->SubmitSourceBuffer( &buffer );
    281 
    282 	return buffer.AudioBytes;
    283 }
    284 
    285 /*
    286 ========================
    287 idSoundVoice_XAudio2::Update
    288 ========================
    289 */
    290 bool idSoundVoice_XAudio2::Update() {
    291 	if ( pSourceVoice == NULL || leadinSample == NULL ) {
    292 		return false;
    293 	}
    294 
    295 	XAUDIO2_VOICE_STATE state;
    296 	pSourceVoice->GetState( &state );
    297 
    298 	const int srcChannels = leadinSample->NumChannels();
    299 
    300 	float pLevelMatrix[ MAX_CHANNELS_PER_VOICE * MAX_CHANNELS_PER_VOICE ] = { 0 };
    301 	CalculateSurround( srcChannels, pLevelMatrix, 1.0f );
    302 
    303 	if ( s_skipHardwareSets.GetBool() ) {
    304 		return true;
    305 	}
    306 
    307 	pSourceVoice->SetOutputMatrix( soundSystemLocal.hardware.pMasterVoice, srcChannels, dstChannels, pLevelMatrix, OPERATION_SET );
    308 
    309 	assert( idMath::Fabs( gain ) <= XAUDIO2_MAX_VOLUME_LEVEL );
    310 	pSourceVoice->SetVolume( gain, OPERATION_SET );
    311 
    312 	SetSampleRate( sampleRate, OPERATION_SET );
    313 
    314 	// we don't do this any longer because we pause and unpause explicitly when the soundworld is paused or unpaused
    315 	// UnPause();
    316 	return true;
    317 }
    318 
    319 /*
    320 ========================
    321 idSoundVoice_XAudio2::IsPlaying
    322 ========================
    323 */
    324 bool idSoundVoice_XAudio2::IsPlaying() {
    325 	if ( pSourceVoice == NULL ) {
    326 		return false;
    327 	}
    328 	XAUDIO2_VOICE_STATE state;
    329 	pSourceVoice->GetState( &state );
    330 	return ( state.BuffersQueued != 0 );
    331 }
    332 
    333 /*
    334 ========================
    335 idSoundVoice_XAudio2::FlushSourceBuffers
    336 ========================
    337 */
    338 void idSoundVoice_XAudio2::FlushSourceBuffers() {
    339 	if ( pSourceVoice != NULL ) {
    340 		pSourceVoice->FlushSourceBuffers();
    341 	}
    342 }
    343 
    344 /*
    345 ========================
    346 idSoundVoice_XAudio2::Pause
    347 ========================
    348 */
    349 void idSoundVoice_XAudio2::Pause() {
    350 	if ( !pSourceVoice || paused ) {
    351 		return;
    352 	}
    353 	if ( s_debugHardware.GetBool() ) {
    354 		idLib::Printf( "%dms: %p pausing %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>" );
    355 	}
    356 	pSourceVoice->Stop( 0, OPERATION_SET );
    357 	paused = true;
    358 }
    359 
    360 /*
    361 ========================
    362 idSoundVoice_XAudio2::UnPause
    363 ========================
    364 */
    365 void idSoundVoice_XAudio2::UnPause() {
    366 	if ( !pSourceVoice || !paused ) {
    367 		return;
    368 	}
    369 	if ( s_debugHardware.GetBool() ) {
    370 		idLib::Printf( "%dms: %p unpausing %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>" );
    371 	}
    372 	pSourceVoice->Start( 0, OPERATION_SET );
    373 	paused = false;
    374 }
    375 
    376 /*
    377 ========================
    378 idSoundVoice_XAudio2::Stop
    379 ========================
    380 */
    381 void idSoundVoice_XAudio2::Stop() {
    382 	if ( !pSourceVoice ) {
    383 		return;
    384 	}
    385 	if ( !paused ) {
    386 		if ( s_debugHardware.GetBool() ) {
    387 			idLib::Printf( "%dms: %p stopping %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>" );
    388 		}
    389 		pSourceVoice->Stop( 0, OPERATION_SET );
    390 		paused = true;
    391 	}
    392 }
    393 
    394 /*
    395 ========================
    396 idSoundVoice_XAudio2::GetAmplitude
    397 ========================
    398 */
    399 float idSoundVoice_XAudio2::GetAmplitude() {
    400 	if ( !hasVUMeter ) {
    401 		return 1.0f;
    402 	}
    403 
    404 	float peakLevels[ MAX_CHANNELS_PER_VOICE ];
    405 	float rmsLevels[ MAX_CHANNELS_PER_VOICE ];
    406 
    407 	XAUDIO2FX_VOLUMEMETER_LEVELS levels;
    408 	levels.ChannelCount = leadinSample->NumChannels();
    409 	levels.pPeakLevels = peakLevels;
    410 	levels.pRMSLevels = rmsLevels;
    411 
    412 	if ( levels.ChannelCount > MAX_CHANNELS_PER_VOICE ) {
    413 		levels.ChannelCount = MAX_CHANNELS_PER_VOICE;
    414 	}
    415 
    416 	if ( pSourceVoice->GetEffectParameters( 0, &levels, sizeof( levels ) ) != S_OK ) {
    417 		return 0.0f;
    418 	}
    419 
    420 	if ( levels.ChannelCount == 1 ) {
    421 		return rmsLevels[0];
    422 	}
    423 
    424 	float rms = 0.0f;
    425 	for ( uint32 i = 0; i < levels.ChannelCount; i++ ) {
    426 		rms += rmsLevels[i];
    427 	}
    428 
    429 	return rms / (float)levels.ChannelCount;
    430 }
    431 
    432 /*
    433 ========================
    434 idSoundVoice_XAudio2::ResetSampleRate
    435 ========================
    436 */
    437 void idSoundVoice_XAudio2::SetSampleRate( uint32 newSampleRate, uint32 operationSet ){
    438 	if ( pSourceVoice == NULL || leadinSample == NULL ) {
    439 		return;
    440 	}
    441 
    442 	sampleRate = newSampleRate;
    443 
    444 	XAUDIO2_FILTER_PARAMETERS filter;
    445 	filter.Type = LowPassFilter;
    446 	filter.OneOverQ = 1.0f;			// [0.0f, XAUDIO2_MAX_FILTER_ONEOVERQ]
    447 	float cutoffFrequency = 1000.0f / Max( 0.01f, occlusion );
    448 	if ( cutoffFrequency * 6.0f >= (float)sampleRate ) {
    449 		filter.Frequency = XAUDIO2_MAX_FILTER_FREQUENCY;
    450 	} else {
    451 		filter.Frequency = 2.0f * idMath::Sin( idMath::PI * cutoffFrequency / (float)sampleRate );
    452 	}
    453 	assert( filter.Frequency >= 0.0f && filter.Frequency <= XAUDIO2_MAX_FILTER_FREQUENCY );
    454 	filter.Frequency = idMath::ClampFloat( 0.0f, XAUDIO2_MAX_FILTER_FREQUENCY, filter.Frequency );
    455 
    456 	pSourceVoice->SetFilterParameters( &filter, operationSet );
    457 
    458 	float freqRatio = pitch * (float)sampleRate / (float)sourceVoiceRate;
    459 	assert( freqRatio >= XAUDIO2_MIN_FREQ_RATIO && freqRatio <= XAUDIO2_MAX_FREQ_RATIO );
    460 	freqRatio = idMath::ClampFloat( XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_MAX_FREQ_RATIO, freqRatio );
    461 
    462 	// if the value specified for maxFreqRatio is too high for the specified format, the call to CreateSourceVoice will fail
    463 	if ( numChannels == 1 ) {
    464 		assert( freqRatio * (float)SYSTEM_SAMPLE_RATE <= XAUDIO2_MAX_RATIO_TIMES_RATE_XMA_MONO );
    465 	} else {
    466 		assert( freqRatio * (float)SYSTEM_SAMPLE_RATE <= XAUDIO2_MAX_RATIO_TIMES_RATE_XMA_MULTICHANNEL );
    467 	}
    468 	pSourceVoice->SetFrequencyRatio( freqRatio, operationSet );
    469 }
    470 
    471 /*
    472 ========================
    473 idSoundVoice_XAudio2::OnBufferStart
    474 ========================
    475 */
    476 void idSoundVoice_XAudio2::OnBufferStart( idSoundSample_XAudio2 * sample, int bufferNumber ) {
    477 	SetSampleRate( sample->SampleRate(), XAUDIO2_COMMIT_NOW );
    478 
    479 	idSoundSample_XAudio2 * nextSample = sample;
    480 	int nextBuffer = bufferNumber + 1;
    481 	if ( nextBuffer == sample->buffers.Num() ) {
    482 		if ( sample == leadinSample ) {
    483 			if ( loopingSample == NULL ) {
    484 				return;
    485 			}
    486 			nextSample = loopingSample;
    487 		}
    488 		nextBuffer = 0;
    489 	}
    490 
    491 	SubmitBuffer( nextSample, nextBuffer, 0 );
    492 }