Files
UnrealEngineUWP/Engine/Source/Runtime/Windows/XAudio2/Private/XAudio2Effects.cpp
Aaron McLeran d2729ee628 Supporting multiple audio devices for multiple PIE and other potential use-cases.
- Created audio device manager which manages weak ref handles to audio devices
- Audio device handles are created with WorldContexts and accessed through UWorld objects
- All access to audio devices are now through audio device handles
- There is a main/default audio device for GEngine for use with the Editor and single worldcontext games

#codereview marc.audy, matthew.griffin

[CL 2477046 by Aaron McLeran in Main branch]
2015-03-12 12:59:33 -04:00

624 lines
20 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
XeAudioDevice.cpp: Unreal XAudio2 Audio interface object.
Unreal is RHS with Y and Z swapped (or technically LHS with flipped axis)
=============================================================================*/
/*------------------------------------------------------------------------------------
Audio includes.
------------------------------------------------------------------------------------*/
#include "XAudio2Device.h"
#include "AudioEffect.h"
#include "XAudio2Effects.h"
#include "Engine.h"
#include "XAudio2Support.h"
#include "AllowWindowsPlatformTypes.h"
#include <xapobase.h>
#include <xapofx.h>
#include <xaudio2fx.h>
#include "HideWindowsPlatformTypes.h"
/*-----------------------------------------------------------------------------
FBandPassFilter
-----------------------------------------------------------------------------*/
class FBandPassFilter
{
private:
float Coefficient0;
float Coefficient1;
float Coefficient2;
float Coefficient3;
float Coefficient4;
float Z0;
float Z1;
float Y0;
float Y1;
inline static float CalculateC( float BandwidthHz, float SampleRate )
{
const float Angle = PI * ( ( BandwidthHz * 0.5f ) / SampleRate );
return FMath::Tan( Angle - 1.0f ) / FMath::Tan( 2.0f * Angle + 1.0f );
}
inline static float CalculateD( float CenterFrequencyHz, float SampleRate )
{
const float Angle = 2.0f * PI * CenterFrequencyHz / SampleRate;
return -FMath::Cos( Angle );
}
public:
/**
* Constructor (default).
*/
FBandPassFilter( void )
: Coefficient0( 0.0f )
, Coefficient1( 0.0f )
, Coefficient2( 0.0f )
, Coefficient3( 0.0f )
, Coefficient4( 0.0f )
, Z0( 0.0f )
, Z1( 0.0f )
, Y0( 0.0f )
, Y1( 0.0f )
{
}
inline void Initialize( float FrequencyHz, float BandwidthHz, float SampleRate )
{
const float C = CalculateC( BandwidthHz, SampleRate );
const float D = CalculateD( FrequencyHz, SampleRate );
const float A0 = 1.0f;
const float A1 = D * ( 1.0f - C );
const float A2 = -C;
const float B0 = 1.0f + C;
const float B1 = 0.0f;
const float B2 = -B0;
Coefficient0 = B0 / A0;
Coefficient1 = +B1 / A0;
Coefficient2 = +B2 / A0;
Coefficient3 = -A1 / A0;
Coefficient4 = -A2 / A0;
Z0 = Z1 = Y0 = Y1 = 0.0f;
}
inline float Process( float Sample )
{
const float Y = Coefficient0 * Sample
+ Coefficient1 * Z0
+ Coefficient2 * Z1
+ Coefficient3 * Y0
+ Coefficient4 * Y1;
Z1 = Z0;
Z0 = Sample;
Y1 = Y0;
Y0 = Y;
return Y;
}
};
/*-----------------------------------------------------------------------------
Global utility classes for generating a radio distortion effect.
-----------------------------------------------------------------------------*/
static FBandPassFilter GFinalBandPassFilter;
/*-----------------------------------------------------------------------------
FXAudio2RadioEffect. Custom XAPO for radio distortion.
-----------------------------------------------------------------------------*/
#define RADIO_CLASS_ID __declspec( uuid( "{5EB8D611-FF96-429d-8365-2DDF89A7C1CD}" ) )
/**
* Custom XAudio2 Audio Processing Object (XAPO) that distorts
* audio samples into having a radio effect applied to them.
*/
class RADIO_CLASS_ID FXAudio2RadioEffect : public CXAPOParametersBase
{
public:
/**
* Constructor.
*
* @param InitialSampleRate The sample rate to process the audio.
*/
FXAudio2RadioEffect( float InitialSampleRate )
: CXAPOParametersBase( &Registration, ( uint8* )Parameters, sizeof( FAudioRadioEffect ), false )
, SampleRate( InitialSampleRate )
{
// Initialize the global audio processing helper classes
GFinalBandPassFilter.Initialize( 2000.0f, 400.0f, SampleRate );
// Setup default values for the parameters to initialize the rest of the global
// audio processing helper classes. See FXAudio2RadioEffect::OnSetParameters().
FAudioRadioEffect DefaultParameters;
SetParameters( &DefaultParameters, sizeof( DefaultParameters ) );
}
/**
* Copies the wave format of the audio for reference.
*
* @note Called by XAudio2 to lock the input and output configurations of an XAPO allowing
* it to do any final initialization before Process is called on the real-time thread.
*/
STDMETHOD( LockForProcess )( UINT32 InputLockedParameterCount,
const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS *pInputLockedParameters,
UINT32 OutputLockedParameterCount,
const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS *pOutputLockedParameters )
{
// Try to lock the process on the base class before attempting to do any initialization here.
HRESULT Result = CXAPOParametersBase::LockForProcess( InputLockedParameterCount,
pInputLockedParameters,
OutputLockedParameterCount,
pOutputLockedParameters );
// Process couldn't be locked. ABORT!
if( FAILED( Result ) )
{
return Result;
}
// Store the wave format locally on this effect to use in the Process() function.
FMemory::Memcpy( &WaveFormat, pInputLockedParameters[0].pFormat, sizeof( WAVEFORMATEX ) );
return S_OK;
}
/**
* Adds a radio distortion to the input buffer if the radio effect is enabled.
*
* @param InputProcessParameterCount Number of elements in pInputProcessParameters.
* @param pInputProcessParameters Input buffer containing audio samples.
* @param OutputProcessParameterCount Number of elements in pOutputProcessParameters.
* @param pOutputProcessParameters Output buffer, which is not touched by this xAPO.
* @param IsEnabled true if this effect is enabled; false, otherwise.
*/
STDMETHOD_( void, Process )( UINT32 InputProcessParameterCount,
const XAPO_PROCESS_BUFFER_PARAMETERS *pInputProcessParameters,
UINT32 OutputProcessParameterCount,
XAPO_PROCESS_BUFFER_PARAMETERS *pOutputProcessParameters,
BOOL IsEnabled )
{
// Verify several condition based on the registration
// properties we used to create the class.
check( IsLocked() );
check( InputProcessParameterCount == 1 );
check( OutputProcessParameterCount == 1 );
check( pInputProcessParameters[0].pBuffer == pOutputProcessParameters[0].pBuffer );
// Check the global volume multiplier because this effect
// will continue to play if the editor loses focus.
if (IsEnabled && FApp::GetVolumeMultiplier() != 0.0f)
{
FAudioRadioEffect* RadioParameters = ( FAudioRadioEffect* )BeginProcess();
check( RadioParameters );
// The total sample size must account for multiple channels.
const uint32 SampleSize = pInputProcessParameters[0].ValidFrameCount * WaveFormat.nChannels;
const float ChebyshevPowerMultiplier = Radio_ChebyshevPowerMultiplier->GetValueOnAnyThread();
const float ChebyshevPower = Radio_ChebyshevPower->GetValueOnAnyThread();
const float ChebyshevCubedMultiplier = Radio_ChebyshevCubedMultiplier->GetValueOnAnyThread();
const float ChebyshevMultiplier = Radio_ChebyshevMultiplier->GetValueOnAnyThread();
// If we have a silent buffer, then allocate the samples. Then, set the sample values
// to zero to avoid getting NANs. Otherwise, the user may hear an annoying popping noise.
if( pInputProcessParameters[0].BufferFlags == XAPO_BUFFER_VALID )
{
float* SampleData = ( float* )pInputProcessParameters[0].pBuffer;
// Process each sample one at a time
for( uint32 SampleIndex = 0; SampleIndex < SampleSize; ++SampleIndex )
{
float Sample = SampleData[SampleIndex];
// Early-out of processing if the sample is zero because a zero sample
// will still create some static even if no audio is playing.
if( Sample == 0.0f )
{
continue;
}
// Waveshape it
const float SampleCubed = Sample * Sample * Sample;
Sample = ( ChebyshevPowerMultiplier * FMath::Pow( Sample, ChebyshevPower ) ) - ( ChebyshevCubedMultiplier * SampleCubed ) + ( ChebyshevMultiplier * Sample );
// Again with the bandpass
Sample = GFinalBandPassFilter.Process( Sample );
// Store the value after done processing
SampleData[SampleIndex] = Sample;
}
EndProcess();
}
}
}
protected:
/**
* Reinitializes the global helper sample
*
* @note Called whenever SetParameters() is called on this XAPO.
*/
void OnSetParameters( const void* Parameters, UINT32 ParameterSize )
{
// The given parameter must be a FAudioRadioEffect struct.
check( ParameterSize == sizeof( FAudioRadioEffect ) );
FAudioRadioEffect& RadioParams = *( FAudioRadioEffect* )Parameters;
}
private:
/** Ring buffer needed by CXAPOParametersBase */
FAudioRadioEffect Parameters[3];
/** Format of the audio we're processing. */
WAVEFORMATEX WaveFormat;
/** The sample rate to process the audio samples. */
const float SampleRate;
/** Console variables to tweak the output */
static TConsoleVariableData<float>* Radio_ChebyshevPowerMultiplier;
static TConsoleVariableData<float>* Radio_ChebyshevPower;
static TConsoleVariableData<float>* Radio_ChebyshevCubedMultiplier;
static TConsoleVariableData<float>* Radio_ChebyshevMultiplier;
/** Registration properties defining this radio effect class. */
static XAPO_REGISTRATION_PROPERTIES Registration;
};
TConsoleVariableData<float>* FXAudio2RadioEffect::Radio_ChebyshevPowerMultiplier = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "Radio_ChebyshevPowerMultiplier" ), 2.0f, TEXT( "A parameter to tweak the radio filter." ), ECVF_Default )->AsVariableFloat();
TConsoleVariableData<float>* FXAudio2RadioEffect::Radio_ChebyshevPower = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "Radio_ChebyshevPower" ), 5.0f, TEXT( "A parameter to tweak the radio filter." ), ECVF_Default )->AsVariableFloat();
TConsoleVariableData<float>* FXAudio2RadioEffect::Radio_ChebyshevCubedMultiplier = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "Radio_ChebyshevCubedMultiplier" ), 5.0f, TEXT( "A parameter to tweak the radio filter." ), ECVF_Default )->AsVariableFloat();
TConsoleVariableData<float>* FXAudio2RadioEffect::Radio_ChebyshevMultiplier = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "Radio_ChebyshevMultiplier" ), 3.0f, TEXT( "A parameter to tweak the radio filter." ), ECVF_Default )->AsVariableFloat();
/** Define the registration properties for the radio effect. */
XAPO_REGISTRATION_PROPERTIES FXAudio2RadioEffect::Registration =
{
__uuidof( FXAudio2RadioEffect ),
TEXT( "FXAudio2RadioEffect" ),
TEXT( "Copyright 1998-2015 Epic Games, Inc. All Rights Reserved." ),
1, 0,
XAPO_FLAG_INPLACE_REQUIRED | XAPO_FLAG_CHANNELS_MUST_MATCH
| XAPO_FLAG_FRAMERATE_MUST_MATCH
| XAPO_FLAG_BITSPERSAMPLE_MUST_MATCH
| XAPO_FLAG_BUFFERCOUNT_MUST_MATCH
| XAPO_FLAG_INPLACE_SUPPORTED,
1, 1, 1, 1
};
/*------------------------------------------------------------------------------------
FXeAudioEffectsManager.
------------------------------------------------------------------------------------*/
/**
* Create voices that pipe the dry or EQ'd sound to the master output
*/
bool FXAudio2EffectsManager::CreateEQPremasterVoices()
{
uint32 SampleRate = UE4_XAUDIO2_SAMPLERATE;
// Create the EQ effect
if( !XAudio2Device->ValidateAPICall( TEXT( "CreateFX (EQ)" ),
CreateFX( __uuidof( FXEQ ), &EQEffect ) ) )
{
return( false );
}
XAUDIO2_EFFECT_DESCRIPTOR EQEffects[] =
{
{ EQEffect, true, SPEAKER_COUNT }
};
XAUDIO2_EFFECT_CHAIN EQEffectChain =
{
1, EQEffects
};
check(XAudio2Device->DeviceProperties != nullptr);
check(XAudio2Device->DeviceProperties->XAudio2 != nullptr);
if( !XAudio2Device->ValidateAPICall( TEXT( "CreateSubmixVoice (EQPremaster)" ),
XAudio2Device->DeviceProperties->XAudio2->CreateSubmixVoice(&EQPremasterVoice, SPEAKER_COUNT, SampleRate, 0, STAGE_EQPREMASTER, NULL, &EQEffectChain)))
{
return( false );
}
if( !XAudio2Device->ValidateAPICall( TEXT( "CreateSubmixVoice (DryPremaster)" ),
XAudio2Device->DeviceProperties->XAudio2->CreateSubmixVoice(&DryPremasterVoice, SPEAKER_COUNT, SampleRate, 0, STAGE_EQPREMASTER, NULL, NULL)))
{
return( false );
}
// Set the output matrix catering for a potential downmix
const uint32 NumChannels = UE4_XAUDIO2_NUMCHANNELS;
FXAudio2Device::GetOutputMatrix(UE4_XAUDIO2_CHANNELMASK, NumChannels);
if( !XAudio2Device->ValidateAPICall( TEXT( "SetOutputMatrix (EQPremaster)" ),
EQPremasterVoice->SetOutputMatrix( NULL, SPEAKER_COUNT, NumChannels, FXAudioDeviceProperties::OutputMixMatrix ) ) )
{
return( false );
}
if( !XAudio2Device->ValidateAPICall( TEXT( "SetOutputMatrix (DryPremaster)" ),
DryPremasterVoice->SetOutputMatrix( NULL, SPEAKER_COUNT, NumChannels, FXAudioDeviceProperties::OutputMixMatrix ) ) )
{
return( false );
}
return( true );
}
/**
* Create a voice that pipes the reverb sounds to the premastering voices
*/
bool FXAudio2EffectsManager::CreateReverbVoice()
{
UINT32 Flags;
uint32 SampleRate = UE4_XAUDIO2_SAMPLERATE;
Flags = 0; // XAUDIO2FX_DEBUG
// Create the reverb effect
if( !XAudio2Device->ValidateAPICall( TEXT( "CreateReverbEffect" ),
XAudio2CreateReverb( &ReverbEffect, Flags ) ) )
{
return( false );
}
XAUDIO2_EFFECT_DESCRIPTOR ReverbEffects[] =
{
{ ReverbEffect, true, 2 }
};
XAUDIO2_EFFECT_CHAIN ReverbEffectChain =
{
1, ReverbEffects
};
XAUDIO2_SEND_DESCRIPTOR SendList[] =
{
{ 0, DryPremasterVoice }
};
const XAUDIO2_VOICE_SENDS ReverbSends =
{
1, SendList
};
if( !XAudio2Device->ValidateAPICall( TEXT( "CreateSubmixVoice (Reverb)" ),
XAudio2Device->DeviceProperties->XAudio2->CreateSubmixVoice(&ReverbEffectVoice, 2, SampleRate, 0, STAGE_REVERB, &ReverbSends, &ReverbEffectChain)))
{
return( false );
}
const float OutputMatrix[SPEAKER_COUNT * 2] =
{
1.0f, 0.0f,
0.0f, 1.0f,
0.7f, 0.7f,
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f
};
if( !XAudio2Device->ValidateAPICall( TEXT( "SetOutputMatrix (Reverb)" ),
ReverbEffectVoice->SetOutputMatrix( DryPremasterVoice, 2, SPEAKER_COUNT, OutputMatrix ) ) )
{
return( false );
}
return( true );
}
/**
* Create a voice that pipes the radio sounds to the master output.
*
* @param XAudio2Device The audio device used by the engine.
*/
bool FXAudio2EffectsManager::CreateRadioVoice()
{
// Grab the sample rate, which is needed to configure the radio distortion effect settings.
const uint32 SampleRate = UE4_XAUDIO2_SAMPLERATE;
// Create the custom XAPO radio distortion effect
FXAudio2RadioEffect* NewRadioEffect = new FXAudio2RadioEffect( SampleRate );
NewRadioEffect->Initialize( NULL, 0 );
RadioEffect = ( IXAPO* )NewRadioEffect;
// Define the effect chain that will be applied to
// the submix voice dedicated to radio distortion.
const bool bRadioEffectEnabled = true;
const int32 OutputChannelCount = SPEAKER_COUNT;
XAUDIO2_EFFECT_DESCRIPTOR RadioEffects[] =
{
{ RadioEffect, bRadioEffectEnabled, OutputChannelCount }
};
XAUDIO2_EFFECT_CHAIN RadioEffectChain =
{
1, RadioEffects
};
// Finally, create the submix voice that holds the radio effect. Sounds (source voices)
// will be piped to this submix voice to receive radio distortion.
if( !XAudio2Device->ValidateAPICall( TEXT( "CreateSubmixVoice (Radio)" ),
XAudio2Device->DeviceProperties->XAudio2->CreateSubmixVoice(&RadioEffectVoice, OutputChannelCount, SampleRate, 0, STAGE_RADIO, NULL, &RadioEffectChain)))
{
return false;
}
const uint32 NumChannels = UE4_XAUDIO2_NUMCHANNELS;
FXAudio2Device::GetOutputMatrix( UE4_XAUDIO2_CHANNELMASK, NumChannels );
// Designate the radio-distorted audio to route to the master voice.
if( !XAudio2Device->ValidateAPICall( TEXT( "SetOutputMatrix (Radio)" ),
RadioEffectVoice->SetOutputMatrix( NULL, SPEAKER_COUNT, NumChannels, FXAudioDeviceProperties::OutputMixMatrix ) ) )
{
return false;
}
return true;
}
/**
* Init all sound effect related code
*/
FXAudio2EffectsManager::FXAudio2EffectsManager(FXAudio2Device* InDevice)
: FAudioEffectsManager(InDevice)
, XAudio2Device(InDevice)
{
check( MIN_FILTER_GAIN >= FXEQ_MIN_GAIN );
check( MAX_FILTER_GAIN <= FXEQ_MAX_GAIN );
check( MIN_FILTER_FREQUENCY >= FXEQ_MIN_FREQUENCY_CENTER );
check( MAX_FILTER_FREQUENCY <= FXEQ_MAX_FREQUENCY_CENTER );
ReverbEffect = NULL;
EQEffect = NULL;
RadioEffect = NULL;
DryPremasterVoice = NULL;
EQPremasterVoice = NULL;
ReverbEffectVoice = NULL;
RadioEffectVoice = NULL;
// Create premaster voices for EQ and dry passes
CreateEQPremasterVoices();
// Create reverb voice
CreateReverbVoice();
// Create radio voice
CreateRadioVoice();
}
/**
* Clean up
*/
FXAudio2EffectsManager::~FXAudio2EffectsManager( void )
{
if( RadioEffectVoice )
{
RadioEffectVoice->DestroyVoice();
}
if( ReverbEffectVoice )
{
ReverbEffectVoice->DestroyVoice();
}
if( DryPremasterVoice )
{
DryPremasterVoice->DestroyVoice();
}
if( EQPremasterVoice )
{
EQPremasterVoice->DestroyVoice();
}
if( RadioEffect )
{
RadioEffect->Release();
}
if( ReverbEffect )
{
ReverbEffect->Release();
}
if( EQEffect )
{
EQEffect->Release();
}
}
/**
* Applies the generic reverb parameters to the XAudio2 hardware
*/
void FXAudio2EffectsManager::SetReverbEffectParameters( const FAudioReverbEffect& ReverbEffectParameters )
{
if( ReverbEffectVoice != NULL )
{
XAUDIO2FX_REVERB_I3DL2_PARAMETERS ReverbParameters;
XAUDIO2FX_REVERB_PARAMETERS NativeParameters;
ReverbParameters.WetDryMix = 100.0f;
ReverbParameters.Room = VolumeToMilliBels( ReverbEffectParameters.Volume * ReverbEffectParameters.Gain, 0 );
ReverbParameters.RoomHF = VolumeToMilliBels( ReverbEffectParameters.GainHF, -45 );
ReverbParameters.RoomRolloffFactor = ReverbEffectParameters.RoomRolloffFactor;
ReverbParameters.DecayTime = ReverbEffectParameters.DecayTime;
ReverbParameters.DecayHFRatio = ReverbEffectParameters.DecayHFRatio;
ReverbParameters.Reflections = VolumeToMilliBels( ReverbEffectParameters.ReflectionsGain, 1000 );
ReverbParameters.ReflectionsDelay = ReverbEffectParameters.ReflectionsDelay;
ReverbParameters.Reverb = VolumeToMilliBels( ReverbEffectParameters.LateGain, 2000 );
ReverbParameters.ReverbDelay = ReverbEffectParameters.LateDelay;
ReverbParameters.Diffusion = ReverbEffectParameters.Diffusion * 100.0f;
ReverbParameters.Density = ReverbEffectParameters.Density * 100.0f;
ReverbParameters.HFReference = DEFAULT_HIGH_FREQUENCY;
ReverbConvertI3DL2ToNative( &ReverbParameters, &NativeParameters );
XAudio2Device->ValidateAPICall( TEXT( "SetEffectParameters (Reverb)" ),
ReverbEffectVoice->SetEffectParameters( 0, &NativeParameters, sizeof( NativeParameters ) ) );
}
}
/**
* Applies the generic EQ parameters to the XAudio2 hardware
*/
void FXAudio2EffectsManager::SetEQEffectParameters( const FAudioEQEffect& EQEffectParameters )
{
if (EQPremasterVoice != NULL)
{
FXEQ_PARAMETERS NativeParameters;
NativeParameters.FrequencyCenter0 = EQEffectParameters.LFFrequency;
NativeParameters.Gain0 = EQEffectParameters.LFGain;
NativeParameters.Bandwidth0 = FXEQ_DEFAULT_BANDWIDTH;
NativeParameters.FrequencyCenter1 = EQEffectParameters.MFCutoffFrequency;
NativeParameters.Gain1 = EQEffectParameters.MFGain;
NativeParameters.Bandwidth1 = EQEffectParameters.MFBandwidth;
NativeParameters.FrequencyCenter2 = EQEffectParameters.HFFrequency;
NativeParameters.Gain2 = EQEffectParameters.HFGain;
NativeParameters.Bandwidth2 = FXEQ_DEFAULT_BANDWIDTH;
NativeParameters.FrequencyCenter3 = FXEQ_DEFAULT_FREQUENCY_CENTER_3;
NativeParameters.Gain3 = FXEQ_DEFAULT_GAIN;
NativeParameters.Bandwidth3 = FXEQ_DEFAULT_BANDWIDTH;
XAudio2Device->ValidateAPICall( TEXT( "SetEffectParameters (EQ)" ),
EQPremasterVoice->SetEffectParameters( 0, &NativeParameters, sizeof( NativeParameters ) ) );
}
}
/**
* Calls the platform specific code to set the parameters that define a radio effect.
*
* @param RadioEffectParameters The new parameters for the radio distortion effect.
*/
void FXAudio2EffectsManager::SetRadioEffectParameters( const FAudioRadioEffect& RadioEffectParameters )
{
XAudio2Device->ValidateAPICall( TEXT( "SetEffectParameters (Radio)" ),
RadioEffectVoice->SetEffectParameters( 0, &RadioEffectParameters, sizeof( RadioEffectParameters ) ) );
}
// end