Files
UnrealEngine/Engine/Source/Runtime/AudioMixer/Private/AudioMixerSource.cpp
2025-05-18 13:04:45 +08:00

2214 lines
78 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AudioMixerSource.h"
#include "Audio/AudioTimingLog.h"
#include "AudioDefines.h"
#include "AudioMixerSourceBuffer.h"
#include "ActiveSound.h"
#include "AudioMixerSourceBuffer.h"
#include "AudioMixerDevice.h"
#include "AudioMixerSourceVoice.h"
#include "AudioMixerTrace.h"
#include "ContentStreaming.h"
#include "IAudioExtensionPlugin.h"
#include "IAudioModulation.h"
#include "ProfilingDebugging/CsvProfiler.h"
#include "Sound/AudioSettings.h"
#include "Sound/SoundModulationDestination.h"
#include "Misc/ScopeRWLock.h"
#include "Templates/Function.h"
#include "Trace/Trace.h"
#include "Engine/Engine.h"
CSV_DECLARE_CATEGORY_MODULE_EXTERN(AUDIOMIXERCORE_API, Audio);
#if UE_AUDIO_PROFILERTRACE_ENABLED
UE_TRACE_EVENT_BEGIN(Audio, MixerSourceStart)
UE_TRACE_EVENT_FIELD(uint32, DeviceId)
UE_TRACE_EVENT_FIELD(uint64, Timestamp)
UE_TRACE_EVENT_FIELD(uint32, PlayOrder)
UE_TRACE_EVENT_FIELD(int32, SourceId)
UE_TRACE_EVENT_FIELD(uint64, ComponentId)
UE_TRACE_EVENT_FIELD(UE::Trace::WideString, Name)
UE_TRACE_EVENT_END()
UE_TRACE_EVENT_BEGIN(Audio, MixerSourceStop)
UE_TRACE_EVENT_FIELD(uint32, DeviceId)
UE_TRACE_EVENT_FIELD(uint64, Timestamp)
UE_TRACE_EVENT_FIELD(uint32, PlayOrder)
UE_TRACE_EVENT_END()
#endif // UE_AUDIO_PROFILERTRACE_ENABLED
static int32 UseListenerOverrideForSpreadCVar = 0;
FAutoConsoleVariableRef CVarUseListenerOverrideForSpread(
TEXT("au.UseListenerOverrideForSpread"),
UseListenerOverrideForSpreadCVar,
TEXT("Zero attenuation override distance stereo panning\n")
TEXT("0: Use actual distance, 1: use listener override"),
ECVF_Default);
static int32 bForceAudioLinkOnAllSourcesCVAr = 0;
FAutoConsoleVariableRef CVarForceAudioLinkOnAllSources(
TEXT("au.AudioLink.ForceOnAllSource"),
bForceAudioLinkOnAllSourcesCVAr,
TEXT("0 (off), 1 (enabled). Will force AudioLink on all Sources (if the plugin is enabled)"),
ECVF_Default);
static uint32 AudioMixerSourceFadeMinCVar = 512;
static FAutoConsoleCommand GSetAudioMixerSourceFadeMin(
TEXT("au.SourceFadeMin"),
TEXT("Sets the length (in samples) of minimum fade when a sound source is stopped. Must be divisible by 4 (vectorization requirement). Ignored for some procedural source types. (Default: 512, Min: 4). \n"),
FConsoleCommandWithArgsDelegate::CreateStatic(
[](const TArray<FString>& Args)
{
if (Args.Num() > 0)
{
const int32 SourceFadeMin = FMath::Max(FCString::Atoi(*Args[0]), 4);
AudioMixerSourceFadeMinCVar = AlignArbitrary(SourceFadeMin, 4);
}
}
)
);
namespace Audio
{
namespace MixerSourcePrivate
{
EMixerSourceSubmixSendStage SubmixSendStageToMixerSourceSubmixSendStage(ESubmixSendStage InSendStage)
{
switch(InSendStage)
{
case ESubmixSendStage::PreDistanceAttenuation:
return EMixerSourceSubmixSendStage::PreDistanceAttenuation;
case ESubmixSendStage::PostDistanceAttenuation:
default:
return EMixerSourceSubmixSendStage::PostDistanceAttenuation;
}
}
} // namespace MixerSourcePrivate
namespace ModulationUtils
{
FSoundModulationDestinationSettings InitRoutedVolumeModulation(const FWaveInstance& InWaveInstance, const USoundWave& InWaveData, const FActiveSound& InActiveSound)
{
return InWaveInstance.GetEffectiveModulationSettings(EModulationDestination::Volume);
}
float GetRoutedVolume(const FWaveInstance& InWaveInstance, const USoundWave& InWaveData, const FActiveSound& InActiveSound)
{
return InWaveInstance.GetEffectiveModulationValue(EModulationDestination::Volume);
}
FSoundModulationDestinationSettings InitRoutedPitchModulation(const FWaveInstance& InWaveInstance, const USoundWave& InWaveData, const FActiveSound& InActiveSound)
{
return InWaveInstance.GetEffectiveModulationSettings(EModulationDestination::Pitch);
}
float GetRoutedPitch(const FWaveInstance& InWaveInstance, const USoundWave& InWaveData, const FActiveSound& InActiveSound)
{
return InWaveInstance.GetEffectiveModulationValue(EModulationDestination::Pitch);
}
FSoundModulationDestinationSettings InitRoutedHighpassModulation(const FWaveInstance& InWaveInstance, const USoundWave& InWaveData, const FActiveSound& InActiveSound)
{
return InWaveInstance.GetEffectiveModulationSettings(EModulationDestination::Highpass);
}
float GetRoutedHighpass(const FWaveInstance& InWaveInstance, const USoundWave& InWaveData, const FActiveSound& InActiveSound)
{
return InWaveInstance.GetEffectiveModulationValue(EModulationDestination::Highpass);
}
FSoundModulationDestinationSettings InitRoutedLowpassModulation(const FWaveInstance& InWaveInstance, const USoundWave& InWaveData, const FActiveSound& InActiveSound)
{
return InWaveInstance.GetEffectiveModulationSettings(EModulationDestination::Lowpass);
}
float GetRoutedLowpass(const FWaveInstance& InWaveInstance, const USoundWave& InWaveData, const FActiveSound& InActiveSound)
{
return InWaveInstance.GetEffectiveModulationValue(EModulationDestination::Lowpass);
}
FSoundModulationDefaultSettings InitRoutedModulation(const FWaveInstance& InWaveInstance, const USoundWave& InWaveData, const FActiveSound* InActiveSound)
{
FSoundModulationDefaultSettings Settings;
if (InActiveSound)
{
Settings.VolumeModulationDestination = InitRoutedVolumeModulation(InWaveInstance, InWaveData, *InActiveSound);
Settings.PitchModulationDestination = InitRoutedPitchModulation(InWaveInstance, InWaveData, *InActiveSound);
Settings.HighpassModulationDestination = InitRoutedHighpassModulation(InWaveInstance, InWaveData, *InActiveSound);
Settings.LowpassModulationDestination = InitRoutedLowpassModulation(InWaveInstance, InWaveData, *InActiveSound);
}
return Settings;
}
FSoundModulationDefaultRoutingSettings UpdateRoutedModulation(const FWaveInstance& InWaveInstance, const USoundWave& InWaveData, const FActiveSound* InActiveSound)
{
FSoundModulationDefaultRoutingSettings NewRouting;
if (InActiveSound)
{
NewRouting.VolumeModulationDestination = InitRoutedVolumeModulation(InWaveInstance, InWaveData, *InActiveSound);
NewRouting.PitchModulationDestination = InitRoutedPitchModulation(InWaveInstance, InWaveData, *InActiveSound);
NewRouting.HighpassModulationDestination = InitRoutedHighpassModulation(InWaveInstance, InWaveData, *InActiveSound);
NewRouting.LowpassModulationDestination = InitRoutedLowpassModulation(InWaveInstance, InWaveData, *InActiveSound);
}
return NewRouting;
}
} // namespace ModulationUtils
FMixerSource::FMixerSource(FAudioDevice* InAudioDevice)
: FSoundSource(InAudioDevice)
, MixerDevice(static_cast<FMixerDevice*>(InAudioDevice))
, MixerBuffer(nullptr)
, MixerSourceVoice(nullptr)
, bBypassingSubmixModulation(false)
, bPreviousBusEnablement(false)
, bPreviousBaseSubmixEnablement(false)
, PreviousAzimuth(-1.0f)
, PreviousPlaybackPercent(0.0f)
, InitializationState(EMixerSourceInitializationState::NotInitialized)
, bPlayedCachedBuffer(false)
, bPlaying(false)
, bLoopCallback(false)
, bIsDone(false)
, bIsEffectTailsDone(false)
, bIsPlayingEffectTails(false)
, bEditorWarnedChangedSpatialization(false)
, bIs3D(false)
, bDebugMode(false)
, bIsVorbis(false)
, bIsStoppingVoicesEnabled(InAudioDevice->IsStoppingVoicesEnabled())
, bSendingAudioToBuses(false)
, bPrevAllowedSpatializationSetting(false)
{
}
FMixerSource::~FMixerSource()
{
#if UE_AUDIO_PROFILERTRACE_ENABLED
FTraceAuxiliary::OnTraceStarted.RemoveAll(this);
#endif // UE_AUDIO_PROFILERTRACE_ENABLED
FreeResources();
}
bool FMixerSource::Is3D(const FSoundBuffer* SoundBuffer) const
{
// If we're not spatialized, we're not 3d.
if (!WaveInstance->GetUseSpatialization())
{
return false;
}
// If we're using object spatialization, we can't be panned in 3d.
if (UseObjectBasedSpatialization())
{
return false;
}
// We've got this far, and sending this source to audiolink, allow before
// stereo check below.
if (AudioLink.IsValid())
{
return true;
}
// 3d panner can't handle more than stereo currently.
if (SoundBuffer->NumChannels > 2)
{
return false;
}
// We are 3d.
return true;
}
bool FMixerSource::Init(FWaveInstance* InWaveInstance)
{
AUDIO_MIXER_TRACE_CPUPROFILER_EVENT_SCOPE(AudioMixerSource::Init);
AUDIO_MIXER_CHECK(MixerBuffer);
AUDIO_MIXER_CHECK(MixerBuffer->IsRealTimeSourceReady());
// We've already been passed the wave instance in PrepareForInitialization, make sure we have the same one
AUDIO_MIXER_CHECK(WaveInstance && WaveInstance == InWaveInstance);
AUDIO_MIXER_CHECK(WaveInstance->WaveData);
LLM_SCOPE(ELLMTag::AudioMixer);
FSoundSource::InitCommon();
NumChannels = WaveInstance->WaveData->NumChannels;
if (!ensure(InWaveInstance))
{
return false;
}
USoundWave* WaveData = WaveInstance->WaveData;
check(WaveData);
if (WaveData->NumChannels == 0)
{
UE_LOG(LogAudioMixer, Warning, TEXT("Soundwave %s has invalid compressed data."), *(WaveData->GetName()));
FreeResources();
return false;
}
// Get the number of frames before creating the buffer
int32 NumFrames = INDEX_NONE;
if (WaveData->DecompressionType != DTYPE_Procedural)
{
check(!WaveData->RawPCMData || WaveData->RawPCMDataSize);
const int32 NumBytes = WaveData->RawPCMDataSize;
if (NumChannels > 0)
{
NumFrames = NumBytes / (WaveData->NumChannels * sizeof(int16));
}
}
// Reset all 'previous' state.
PreviousSubmixResolved.Reset();
bPreviousBusEnablement = false;
bPreviousBaseSubmixEnablement = false;
PreviousAzimuth = -1.f;
PreviousPlaybackPercent = 0.f;
PreviousSubmixSends.Reset();
// Unfortunately, we need to know if this is a vorbis source since channel maps are different for 5.1 vorbis files
bIsVorbis = WaveData->bDecompressedFromOgg;
bIsStoppingVoicesEnabled = AudioDevice->IsStoppingVoicesEnabled();
bIsStopping = false;
bIsEffectTailsDone = true;
bIsDone = false;
bBypassingSubmixModulation = false;
FSoundBuffer* SoundBuffer = static_cast<FSoundBuffer*>(MixerBuffer);
if (SoundBuffer->NumChannels > 0)
{
CSV_SCOPED_TIMING_STAT(Audio, InitSources);
SCOPE_CYCLE_COUNTER(STAT_AudioSourceInitTime);
AUDIO_MIXER_CHECK(MixerDevice);
MixerSourceVoice = MixerDevice->GetMixerSourceVoice();
if (!MixerSourceVoice)
{
FreeResources();
UE_LOG(LogAudioMixer, Warning, TEXT("Failed to get a mixer source voice for sound %s."), *InWaveInstance->GetName());
return false;
}
// Initialize the source voice with the necessary format information
FMixerSourceVoiceInitParams InitParams;
InitParams.SourceListener = this;
InitParams.NumInputChannels = WaveData->NumChannels;
InitParams.NumInputFrames = NumFrames;
InitParams.SourceVoice = MixerSourceVoice;
InitParams.bUseHRTFSpatialization = UseObjectBasedSpatialization();
// in this file once spat override is implemented
InitParams.bIsExternalSend = MixerDevice->GetCurrentSpatializationPluginInterfaceInfo().bSpatializationIsExternalSend;
InitParams.bIsSoundfield = WaveInstance->bIsAmbisonics && (WaveData->NumChannels == 4);
FActiveSound* ActiveSound = WaveInstance->ActiveSound;
InitParams.ModulationSettings = ModulationUtils::InitRoutedModulation(*WaveInstance, *WaveData, ActiveSound);
// Copy quantization request data
if (WaveInstance->QuantizedRequestData)
{
InitParams.QuantizedRequestData = *WaveInstance->QuantizedRequestData;
}
if (WaveInstance->bIsAmbisonics && (WaveData->NumChannels != 4))
{
UE_LOG(LogAudioMixer, Warning, TEXT("Sound wave %s was flagged as being ambisonics but had a channel count of %d. Currently the audio engine only supports FOA sources that have four channels."), *InWaveInstance->GetName(), WaveData->NumChannels);
}
if (ActiveSound)
{
InitParams.AudioComponentUserID = WaveInstance->ActiveSound->GetAudioComponentUserID();
#if AUDIO_MIXER_ENABLE_DEBUG_MODE
if (InitParams.AudioComponentUserID.IsNone())
{
InitParams.AudioComponentUserID = ActiveSound->GetSound()->GetFName();
}
#endif // AUDIO_MIXER_ENABLE_DEBUG_MODE
InitParams.AudioComponentID = WaveInstance->ActiveSound->GetAudioComponentID();
}
InitParams.EnvelopeFollowerAttackTime = WaveInstance->EnvelopeFollowerAttackTime;
InitParams.EnvelopeFollowerReleaseTime = WaveInstance->EnvelopeFollowerReleaseTime;
InitParams.SourceEffectChainId = 0;
InitParams.SourceBufferListener = WaveInstance->SourceBufferListener;
InitParams.bShouldSourceBufferListenerZeroBuffer = WaveInstance->bShouldSourceBufferListenerZeroBuffer;
if (WaveInstance->bShouldUseAudioLink || bForceAudioLinkOnAllSourcesCVAr)
{
if (IAudioLinkFactory* LinkFactory = MixerDevice->GetAudioLinkFactory())
{
IAudioLinkFactory::FAudioLinkSourcePushedCreateArgs CreateArgs;
if (WaveInstance->AudioLinkSettingsOverride)
{
CreateArgs.Settings = WaveInstance->AudioLinkSettingsOverride->GetProxy();
}
else
{
CreateArgs.Settings = GetDefault<UAudioLinkSettingsAbstract>(LinkFactory->GetSettingsClass())->GetProxy();
}
CreateArgs.OwnerName = *WaveInstance->GetName(); // <-- FIXME: String FName conversion.
CreateArgs.NumChannels = SoundBuffer->NumChannels;
CreateArgs.NumFramesPerBuffer = MixerDevice->GetBufferLength();
CreateArgs.SampleRate = MixerDevice->GetSampleRate();
CreateArgs.TotalNumFramesInSource = NumTotalFrames;
AudioLink = LinkFactory->CreateSourcePushedAudioLink(CreateArgs);
InitParams.AudioLink = AudioLink;
}
}
// Source manager needs to know if this is a vorbis source for rebuilding speaker maps
InitParams.bIsVorbis = bIsVorbis;
// Support stereo by default
// Check the min number of channels the source effect chain supports
// We don't want to instantiate the effect chain if it has an effect that doesn't support its channel count
// E.g. we shouldn't instantiate a chain on a quad source if there is an effect that only supports stereo
InitParams.SourceEffectChainMaxSupportedChannels = WaveInstance->SourceEffectChain ?
WaveInstance->SourceEffectChain->GetSupportedChannelCount() :
USoundEffectSourcePreset::DefaultSupportedChannels;
if (InitParams.NumInputChannels <= InitParams.SourceEffectChainMaxSupportedChannels)
{
if (WaveInstance->SourceEffectChain)
{
InitParams.SourceEffectChainId = WaveInstance->SourceEffectChain->GetUniqueID();
for (int32 i = 0; i < WaveInstance->SourceEffectChain->Chain.Num(); ++i)
{
InitParams.SourceEffectChain.Add(WaveInstance->SourceEffectChain->Chain[i]);
InitParams.bPlayEffectChainTails = WaveInstance->SourceEffectChain->bPlayEffectChainTails;
}
}
// Only need to care about effect chain tails finishing if we're told to play them
if (InitParams.bPlayEffectChainTails)
{
bIsEffectTailsDone = false;
}
// Setup the bus Id if this source is a bus
if (WaveData->bIsSourceBus)
{
// We need to check if the source bus has an audio bus specified
USoundSourceBus* SoundSourceBus = CastChecked<USoundSourceBus>(WaveData);
// If it does, we will use that audio bus as the source of the audio data for the source bus
if (SoundSourceBus->AudioBus)
{
InitParams.AudioBusId = SoundSourceBus->AudioBus->GetUniqueID();
InitParams.AudioBusChannels = (int32)SoundSourceBus->AudioBus->GetNumChannels();
}
else
{
InitParams.AudioBusId = WaveData->GetUniqueID();
InitParams.AudioBusChannels = WaveData->NumChannels;
}
if (!WaveData->IsLooping())
{
InitParams.SourceBusDuration = WaveData->GetDuration();
}
}
}
// Toggle muting the source if sending only to output bus.
// This can get set even if the source doesn't have bus sends since bus sends can be dynamically enabled.
InitParams.bEnableBusSends = WaveInstance->bEnableBusSends;
InitParams.bEnableBaseSubmix = WaveInstance->bEnableBaseSubmix && !bForceAudioLinkOnAllSourcesCVAr;
InitParams.bEnableSubmixSends = WaveInstance->bEnableSubmixSends;
InitParams.PlayOrder = WaveInstance->GetPlayOrder();
InitParams.ActiveSoundPlayOrder = WaveInstance->ActiveSound != nullptr ? WaveInstance->ActiveSound->GetPlayOrder() : INDEX_NONE;
bPreviousBusEnablement = WaveInstance->bEnableBusSends;
DynamicBusSendInfos.Reset();
SetupBusData(InitParams.AudioBusSends, InitParams.bEnableBusSends);
// Don't set up any submixing if we're set to output to bus only
// If we're spatializing using HRTF and its an external send, don't need to setup a default/base submix send to master or EQ submix
// We'll only be using non-default submix sends (e.g. reverb).
if (!(InitParams.bUseHRTFSpatialization && InitParams.bIsExternalSend))
{
FMixerSubmixWeakPtr SubmixPtr;
// If a sound specifies a base submix manually, always use that
if (WaveInstance->SoundSubmix)
{
SubmixPtr = MixerDevice->GetSubmixInstance(WaveInstance->SoundSubmix);
}
else
{
// Retrieve the base default submix if one is not explicitly set
SubmixPtr = MixerDevice->GetBaseDefaultSubmix();
}
FMixerSourceSubmixSend SubmixSend;
SubmixSend.Submix = SubmixPtr;
SubmixSend.SubmixSendStage = EMixerSourceSubmixSendStage::PostDistanceAttenuation;
SubmixSend.SendLevel = InitParams.bEnableBaseSubmix;
SubmixSend.bIsMainSend = true;
SubmixSend.SoundfieldFactory = MixerDevice->GetFactoryForSubmixInstance(SubmixSend.Submix);
InitParams.SubmixSends.Add(SubmixSend);
bPreviousBaseSubmixEnablement = InitParams.bEnableBaseSubmix;
}
else
{
// Warn about sending a source marked as Binaural directly to a soundfield submix:
// This is a bit of a gray area as soundfield submixes are intended to be their own spatial format
// So to send a source to this, and also flagging the source as Binaural are probably conflicting forms of spatialazition.
FMixerSubmixWeakPtr SubmixWeakPtr = MixerDevice->GetSubmixInstance(WaveInstance->SoundSubmix);
if (FMixerSubmixPtr SubmixPtr = SubmixWeakPtr.Pin())
{
if ((SubmixPtr->IsSoundfieldSubmix() || SubmixPtr->IsSoundfieldEndpointSubmix()))
{
UE_LOG(LogAudioMixer, Warning, TEXT("Ignoring soundfield Base Submix destination being set on SoundWave (%s) because spatialization method is set to Binaural.")
, *InWaveInstance->GetName());
}
bBypassingSubmixModulation = true;
}
}
// Add submix sends for this source
for (FSoundSubmixSendInfo& SendInfo : WaveInstance->SoundSubmixSends)
{
if (SendInfo.SoundSubmix != nullptr)
{
FMixerSourceSubmixSend SubmixSend;
SubmixSend.Submix = MixerDevice->GetSubmixInstance(SendInfo.SoundSubmix);
SubmixSend.SubmixSendStage = EMixerSourceSubmixSendStage::PostDistanceAttenuation;
if (SendInfo.SendStage == ESubmixSendStage::PreDistanceAttenuation)
{
SubmixSend.SubmixSendStage = EMixerSourceSubmixSendStage::PreDistanceAttenuation;
}
if (!WaveInstance->bEnableSubmixSends)
{
SubmixSend.SendLevel = 0.0f;
}
else
{
SubmixSend.SendLevel = SendInfo.SendLevel;
}
SubmixSend.bIsMainSend = false;
SubmixSend.SoundfieldFactory = MixerDevice->GetFactoryForSubmixInstance(SubmixSend.Submix);
InitParams.SubmixSends.Add(SubmixSend);
}
}
// Loop through all submix sends to figure out what speaker maps this source is using
for (FMixerSourceSubmixSend& Send : InitParams.SubmixSends)
{
FMixerSubmixPtr SubmixPtr = Send.Submix.Pin();
if (SubmixPtr.IsValid())
{
FRWScopeLock Lock(ChannelMapLock, SLT_Write);
ChannelMap.Reset();
}
}
// Check to see if this sound has been flagged to be in debug mode
#if AUDIO_MIXER_ENABLE_DEBUG_MODE
InitParams.DebugName = WaveInstance->GetName();
bool bIsDebug = false;
FString WaveInstanceName = WaveInstance->GetName(); //-V595
FString TestName = GEngine->GetAudioDeviceManager()->GetDebugger().GetAudioMixerDebugSoundName();
if (!TestName.IsEmpty() && WaveInstanceName.Contains(TestName))
{
bDebugMode = true;
InitParams.bIsDebugMode = bDebugMode;
}
#endif
UE_CLOG(Audio::MatchesLogFilter(*WaveInstance->GetName()), LogAudioTiming, Verbose,
TEXT("FMixerSource::Init Name=%s,BufferType=%d,CachedRealtimeFirstBuffer=0x%p"),*WaveInstance->GetName(),
(int32)MixerBuffer->GetType(),WaveData->CachedRealtimeFirstBuffer);
// Whether or not we're 3D
bIs3D = Is3D(SoundBuffer);
// Pass on the fact that we're 3D to the init params
InitParams.bIs3D = bIs3D;
// Grab the source's reverb plugin settings
InitParams.SpatializationPluginSettings = UseSpatializationPlugin() ? WaveInstance->SpatializationPluginSettings : nullptr;
// Grab the source's occlusion plugin settings
InitParams.OcclusionPluginSettings = UseOcclusionPlugin() ? WaveInstance->OcclusionPluginSettings : nullptr;
// Grab the source's reverb plugin settings
InitParams.ReverbPluginSettings = UseReverbPlugin() ? WaveInstance->ReverbPluginSettings : nullptr;
// Grab the source's source data override plugin settings
InitParams.SourceDataOverridePluginSettings = UseSourceDataOverridePlugin() ? WaveInstance->SourceDataOverridePluginSettings : nullptr;
// Update the buffer sample rate to the wave instance sample rate, as it could have changed during decoder parse.
MixerBuffer->InitSampleRate(WaveData->GetSampleRateForCurrentPlatform());
MixerBuffer->InitNumFrames(WaveData->GetNumFrames());
// Retrieve the raw pcm buffer data and the precached buffers before initializing so we can avoid having USoundWave ptrs in audio renderer thread
EBufferType::Type BufferType = MixerBuffer->GetType();
if (BufferType == EBufferType::PCM || BufferType == EBufferType::PCMPreview)
{
FRawPCMDataBuffer RawPCMDataBuffer;
MixerBuffer->GetPCMData(&RawPCMDataBuffer.Data, &RawPCMDataBuffer.DataSize);
MixerSourceBuffer->SetPCMData(RawPCMDataBuffer);
}
#if PLATFORM_NUM_AUDIODECOMPRESSION_PRECACHE_BUFFERS > 0
else if (BufferType == EBufferType::PCMRealTime || BufferType == EBufferType::Streaming)
{
if (WaveData->CachedRealtimeFirstBuffer)
{
const uint32 NumPrecacheSamples = (uint32)(WaveData->NumPrecacheFrames * WaveData->NumChannels);
const uint32 BufferSize = NumPrecacheSamples * sizeof(int16) * PLATFORM_NUM_AUDIODECOMPRESSION_PRECACHE_BUFFERS;
TArray<uint8> PrecacheBufferCopy;
PrecacheBufferCopy.AddUninitialized(BufferSize);
FMemory::Memcpy(PrecacheBufferCopy.GetData(), WaveData->CachedRealtimeFirstBuffer, BufferSize);
MixerSourceBuffer->SetCachedRealtimeFirstBuffers(MoveTemp(PrecacheBufferCopy));
}
}
#endif
#if UE_AUDIO_PROFILERTRACE_ENABLED
FTraceAuxiliary::OnTraceStarted.AddRaw(this, &FMixerSource::OnTraceStarted);
#endif // UE_AUDIO_PROFILERTRACE_ENABLED
// Pass the decompression state off to the mixer source buffer if it hasn't already done so
ICompressedAudioInfo* Decoder = MixerBuffer->GetDecompressionState(true);
MixerSourceBuffer->SetDecoder(Decoder);
// Hand off the mixer source buffer decoder
InitParams.MixerSourceBuffer = MixerSourceBuffer;
MixerSourceBuffer = nullptr;
if (MixerSourceVoice->Init(InitParams))
{
// Initialize the propagation interface as soon as we have a valid source id
if (AudioDevice->SourceDataOverridePluginInterface)
{
uint32 SourceId = MixerSourceVoice->GetSourceId();
AudioDevice->SourceDataOverridePluginInterface->OnInitSource(SourceId, InitParams.AudioComponentUserID, InitParams.SourceDataOverridePluginSettings);
}
InitializationState = EMixerSourceInitializationState::Initialized;
Update();
return true;
}
else
{
InitializationState = EMixerSourceInitializationState::NotInitialized;
UE_LOG(LogAudioMixer, Warning, TEXT("Failed to initialize mixer source voice '%s'."), *InWaveInstance->GetName());
}
}
else
{
UE_LOG(LogAudioMixer, Warning, TEXT("Num channels was 0 for sound buffer '%s'."), *InWaveInstance->GetName());
}
FreeResources();
return false;
}
void FMixerSource::SetupBusData(TArray<FInitAudioBusSend>* OutAudioBusSends, bool bEnableBusSends)
{
for (int32 BusSendType = 0; BusSendType < (int32)EBusSendType::Count; ++BusSendType)
{
// And add all the source bus sends
for (FSoundSourceBusSendInfo& SendInfo : WaveInstance->BusSends[BusSendType])
{
// Avoid redoing duplicate code for sending audio to source bus or audio bus. Most of it is the same other than the bus id.
auto SetupBusSend = [this](TArray<FInitAudioBusSend>* AudioBusSends, const FSoundSourceBusSendInfo& InSendInfo, int32 InBusSendType, uint32 InBusId, bool bEnableBusSends, int32 InBusChannels)
{
FInitAudioBusSend BusSend;
BusSend.AudioBusId = InBusId;
BusSend.BusChannels = InBusChannels;
if(bEnableBusSends)
{
BusSend.SendLevel = InSendInfo.SendLevel;
}
else
{
BusSend.SendLevel = 0;
}
if (AudioBusSends)
{
AudioBusSends[InBusSendType].Add(BusSend);
}
FDynamicBusSendInfo NewDynamicBusSendInfo;
NewDynamicBusSendInfo.SendLevel = InSendInfo.SendLevel;
NewDynamicBusSendInfo.BusId = BusSend.AudioBusId;
NewDynamicBusSendInfo.BusSendLevelControlMethod = InSendInfo.SourceBusSendLevelControlMethod;
NewDynamicBusSendInfo.BusSendType = (EBusSendType)InBusSendType;
NewDynamicBusSendInfo.MinSendLevel = InSendInfo.MinSendLevel;
NewDynamicBusSendInfo.MaxSendLevel = InSendInfo.MaxSendLevel;
NewDynamicBusSendInfo.MinSendDistance = InSendInfo.MinSendDistance;
NewDynamicBusSendInfo.MaxSendDistance = InSendInfo.MaxSendDistance;
NewDynamicBusSendInfo.CustomSendLevelCurve = InSendInfo.CustomSendLevelCurve;
// Copy the bus SourceBusSendInfo structs to a local copy so we can update it in the update tick
bool bIsNew = true;
for (FDynamicBusSendInfo& BusSendInfo : DynamicBusSendInfos)
{
if (BusSendInfo.BusId == NewDynamicBusSendInfo.BusId)
{
BusSendInfo = NewDynamicBusSendInfo;
BusSendInfo.bIsInit = false;
bIsNew = false;
break;
}
}
if (bIsNew)
{
DynamicBusSendInfos.Add(NewDynamicBusSendInfo);
}
// Flag that we're sending audio to buses so we can check for updates to send levels
bSendingAudioToBuses = true;
};
// Retrieve bus id of the audio bus to use
if (SendInfo.SoundSourceBus)
{
uint32 BusId;
int32 BusChannels;
// Either use the bus id of the source bus's audio bus id if it was specified
if (SendInfo.SoundSourceBus->AudioBus)
{
BusId = SendInfo.SoundSourceBus->AudioBus->GetUniqueID();
BusChannels = (int32)SendInfo.SoundSourceBus->AudioBus->GetNumChannels();
}
else
{
// otherwise, use the id of the source bus itself (for an automatic source bus)
BusId = SendInfo.SoundSourceBus->GetUniqueID();
BusChannels = SendInfo.SoundSourceBus->NumChannels;
}
// Call lambda w/ the correctly derived bus id
SetupBusSend(OutAudioBusSends, SendInfo, BusSendType, BusId, bEnableBusSends, BusChannels);
}
if (SendInfo.AudioBus)
{
// Only need to send audio to just the specified audio bus
uint32 BusId = SendInfo.AudioBus->GetUniqueID();
int32 BusChannels = (int32)SendInfo.AudioBus->AudioBusChannels + 1;
// Note we will be sending audio to both the specified source bus and the audio bus with the same send level
SetupBusSend(OutAudioBusSends, SendInfo, BusSendType, BusId, bEnableBusSends, BusChannels);
}
}
}
}
void FMixerSource::Update()
{
CSV_SCOPED_TIMING_STAT(Audio, UpdateSources);
SCOPE_CYCLE_COUNTER(STAT_AudioUpdateSources);
LLM_SCOPE(ELLMTag::AudioMixer);
if (!WaveInstance || !MixerSourceVoice || Paused || InitializationState == EMixerSourceInitializationState::NotInitialized)
{
return;
}
AUDIO_MIXER_TRACE_CPUPROFILER_EVENT_SCOPE(FMixerSource::Update);
// if MarkAsGarbage() was called, WaveInstance->WaveData is null
if (!WaveInstance->WaveData)
{
StopNow();
return;
}
++TickCount;
UE_CLOG(Audio::MatchesLogFilter(*WaveInstance->GetName()), LogAudioTiming, Verbose,
TEXT("FMixerSource::Update, Name=%s, StartTime=%.2f bProcedural=%d, bIsSourceBus=%d, CachedRealTimeFirstBuffer=0x%p, MixerSourceBufferValid=%s, TickCount=%d"),
*WaveInstance->GetName(), WaveInstance->StartTime, WaveInstance->WaveData->bProcedural, WaveInstance->WaveData->bIsSourceBus,
WaveInstance->WaveData->CachedRealtimeFirstBuffer,ToCStr(LexToString(MixerSourceBuffer.IsValid())), TickCount);
// Allow plugins to override any data in a waveinstance
if (AudioDevice->SourceDataOverridePluginInterface && WaveInstance->bEnableSourceDataOverride)
{
uint32 SourceId = MixerSourceVoice->GetSourceId();
int32 ListenerIndex = WaveInstance->ActiveSound->GetClosestListenerIndex();
FTransform ListenerTransform;
AudioDevice->GetListenerTransform(ListenerIndex, ListenerTransform);
AudioDevice->SourceDataOverridePluginInterface->GetSourceDataOverrides(SourceId, ListenerTransform, WaveInstance);
}
// AudioLink, push state if we're enabled and 3d.
if (bIs3D && AudioLink.IsValid())
{
IAudioLinkSourcePushed::FOnUpdateWorldStateParams Params;
Params.WorldTransform = WaveInstance->ActiveSound->Transform;
AudioLink->OnUpdateWorldState(Params);
}
UpdateModulation();
UpdatePitch();
UpdateVolume();
UpdateSpatialization();
UpdateEffects();
UpdateSourceBusSends();
UpdateChannelMaps();
UpdateRelativeRenderCost();
#if ENABLE_AUDIO_DEBUG
UpdateCPUCoreUtilization();
Audio::FAudioDebugger::DrawDebugInfo(*this);
#endif // ENABLE_AUDIO_DEBUG
}
bool FMixerSource::PrepareForInitialization(FWaveInstance* InWaveInstance)
{
LLM_SCOPE(ELLMTag::AudioMixer);
if (!ensure(InWaveInstance))
{
return false;
}
// We are currently not supporting playing audio on a controller
if (InWaveInstance->OutputTarget == EAudioOutputTarget::Controller)
{
return false;
}
AUDIO_MIXER_TRACE_CPUPROFILER_EVENT_SCOPE(AudioMixerSource::PrepareForInitialization);
// We are not initialized yet. We won't be until the sound file finishes loading and parsing the header.
InitializationState = EMixerSourceInitializationState::Initializing;
// Reset so next instance will warn if algorithm changes in-flight
bEditorWarnedChangedSpatialization = false;
const bool bIsSeeking = InWaveInstance->StartTime > 0.0f;
check(InWaveInstance);
check(AudioDevice);
check(!MixerBuffer);
MixerBuffer = FMixerBuffer::Init(AudioDevice, InWaveInstance->WaveData, bIsSeeking /* bForceRealtime */);
if (!MixerBuffer)
{
FreeResources(); // APM: maybe need to call this here too?
return false;
}
// WaveData must be valid beyond this point, otherwise MixerBuffer
// would have failed to init.
check(InWaveInstance->WaveData);
USoundWave& SoundWave = *InWaveInstance->WaveData;
WaveInstance = InWaveInstance;
LPFFrequency = MAX_FILTER_FREQUENCY;
HPFFrequency = 0.0f;
bIsDone = false;
// Not all wave data types have a non-zero duration
if (SoundWave.Duration > 0.0f)
{
if (!SoundWave.bIsSourceBus)
{
NumTotalFrames = SoundWave.Duration * SoundWave.GetSampleRateForCurrentPlatform();
check(NumTotalFrames > 0);
}
else if (!SoundWave.IsLooping())
{
NumTotalFrames = SoundWave.Duration * AudioDevice->GetSampleRate();
check(NumTotalFrames > 0);
}
StartFrame = FMath::Clamp<int32>((InWaveInstance->StartTime / SoundWave.Duration) * NumTotalFrames, 0, NumTotalFrames);
}
check(!MixerSourceBuffer.IsValid());
// Active sound instance ID is the audio component ID of active sound.
uint64 InstanceID = 0;
uint32 PlayOrder = 0;
bool bActiveSoundIsPreviewSound = false;
TArray<FAudioParameter> DefaultParameters;
FActiveSound* ActiveSound = WaveInstance->ActiveSound;
if (ActiveSound)
{
InstanceID = ActiveSound->GetAudioComponentID();
PlayOrder = ActiveSound->GetPlayOrder();
bActiveSoundIsPreviewSound = ActiveSound->bIsPreviewSound;
if (Audio::IParameterTransmitter* Transmitter = ActiveSound->GetTransmitter())
{
// This copying of parameters is for the case of virtual loop realization.
// The most up-to-date parameters exist on the instance transmitter.
Transmitter->CopyParameters(DefaultParameters);
SoundWave.InitParameters(DefaultParameters);
}
}
FMixerSourceBufferInitArgs BufferInitArgs;
BufferInitArgs.AudioDeviceID = AudioDevice->DeviceID;
BufferInitArgs.AudioComponentID = InstanceID;
BufferInitArgs.InstanceID = GetTransmitterID(InstanceID, WaveInstance->WaveInstanceHash, PlayOrder);
BufferInitArgs.SampleRate = AudioDevice->GetSampleRate();
BufferInitArgs.AudioMixerNumOutputFrames = MixerDevice->GetNumOutputFrames();
BufferInitArgs.Buffer = MixerBuffer;
BufferInitArgs.SoundWave = &SoundWave;
BufferInitArgs.LoopingMode = InWaveInstance->LoopingMode;
BufferInitArgs.bIsSeeking = bIsSeeking;
BufferInitArgs.bIsPreviewSound = bActiveSoundIsPreviewSound;
BufferInitArgs.StartTime = InWaveInstance->StartTime;
MixerSourceBuffer = FMixerSourceBuffer::Create(BufferInitArgs, MoveTemp(DefaultParameters));
if (!MixerSourceBuffer.IsValid())
{
FreeResources();
// Guarantee that this wave instance does not try to replay by disabling looping.
WaveInstance->LoopingMode = LOOP_Never;
if (ensure(ActiveSound))
{
ActiveSound->bShouldRemainActiveIfDropped = false;
}
}
UE_CLOG(Audio::MatchesLogFilter(*WaveInstance->GetName()), LogAudioTiming, Verbose,
TEXT("FMixerSource::PrepareForInitialization, Name=%s, StartTime=%.2f bProcedural=%d, bIsSourceBus=%d, CachedRealTimeFirstBuffer=0x%p, MixerSourceBufferValid=%s"),
*WaveInstance->GetName(), WaveInstance->StartTime, WaveInstance->WaveData->bProcedural, WaveInstance->WaveData->bIsSourceBus,
WaveInstance->WaveData->CachedRealtimeFirstBuffer,ToCStr(LexToString(MixerSourceBuffer.IsValid())));
return MixerSourceBuffer.IsValid();
}
bool FMixerSource::IsPreparedToInit()
{
LLM_SCOPE(ELLMTag::AudioMixer);
AUDIO_MIXER_TRACE_CPUPROFILER_EVENT_SCOPE(AudioMixerSource::IsPreparedToInit);
if (MixerBuffer && MixerBuffer->IsRealTimeSourceReady())
{
check(MixerSourceBuffer.IsValid());
// Check if we have a realtime audio task already (doing first decode)
if (MixerSourceBuffer->IsAsyncTaskInProgress())
{
const bool bAsyncTaskDone = MixerSourceBuffer->IsAsyncTaskDone();
UE_CLOG(Audio::MatchesLogFilter(*WaveInstance->GetName()), LogAudioTiming, Verbose,
TEXT("FMixerSource::IsPreparedToInit (not ready), Name=%s, StartTime=%.2f bProcedural=%d, bIsSourceBus=%d, CachedRealTimeFirstBuffer=0x%p, IsAyncTaskDone=%s, (IsAsyncTaskInProgress)"),
*WaveInstance->GetName(), WaveInstance->StartTime, WaveInstance->WaveData->bProcedural, WaveInstance->WaveData->bIsSourceBus, WaveInstance->WaveData->CachedRealtimeFirstBuffer,ToCStr(LexToString(bAsyncTaskDone)));
// not ready
return bAsyncTaskDone;
}
else if (WaveInstance)
{
if (WaveInstance->WaveData->bIsSourceBus)
{
// Buses don't need to do anything to play audio
return true;
}
else
{
// Now check to see if we need to kick off a decode the first chunk of audio
const EBufferType::Type BufferType = MixerBuffer->GetType();
if ((BufferType == EBufferType::PCMRealTime || BufferType == EBufferType::Streaming) && WaveInstance->WaveData && !(WaveInstance->WaveData->bProcedural && DirectProceduralRenderingCVar))
{
// If any of these conditions meet, we need to do an initial async decode before we're ready to start playing the sound
if (WaveInstance->StartTime > 0.0f || WaveInstance->WaveData->bProcedural || WaveInstance->WaveData->bIsSourceBus || !WaveInstance->WaveData->CachedRealtimeFirstBuffer)
{
// Before reading more PCMRT data, we first need to seek the buffer
if (WaveInstance->IsSeekable())
{
MixerBuffer->Seek(WaveInstance->StartTime);
}
check(MixerSourceBuffer.IsValid());
UE_CLOG(Audio::MatchesLogFilter(*WaveInstance->GetName()), LogAudioTiming, Verbose,
TEXT("FMixerSource::IsPreparedToInit (not ready), Name=%s, StartTime=%.2f bProcecural=%d, bIsSourceBus=%d, CachedRealTimeFirstBuffer=0x%p, (Kicking Off Initial Async Decode)"),
*WaveInstance->GetName(), WaveInstance->StartTime, WaveInstance->WaveData->bProcedural, WaveInstance->WaveData->bIsSourceBus, WaveInstance->WaveData->CachedRealtimeFirstBuffer);
ICompressedAudioInfo* Decoder = MixerBuffer->GetDecompressionState(false);
MixerSourceBuffer->ReadMoreRealtimeData(Decoder, 0, EBufferReadMode::Asynchronous);
// not ready
return false;
}
}
}
}
return true;
}
UE_CLOG(Audio::MatchesLogFilter(*WaveInstance->GetName()), LogAudioTiming, Verbose,
TEXT("FMixerSource::IsPreparedToInit (not ready), Name=%s, StartTime=%.2f bProcedural=%d, bIsSourceBus=%d, CachedRealTimeFirstBuffer=0x%p, MixerBuffer=0x%p, IsRealTimeSourceReady=%s (realtime source not ready, or no mixer buffer)"),
*WaveInstance->GetName(), WaveInstance->StartTime, WaveInstance->WaveData->bProcedural, WaveInstance->WaveData->bIsSourceBus, WaveInstance->WaveData->CachedRealtimeFirstBuffer,
MixerBuffer, ToCStr(LexToString(MixerBuffer->IsRealTimeSourceReady())));
return false;
}
bool FMixerSource::IsInitialized() const
{
return InitializationState == EMixerSourceInitializationState::Initialized;
}
void FMixerSource::Play()
{
if (!WaveInstance)
{
return;
}
// Don't restart the sound if it was stopping when we paused, just stop it.
if (Paused && (bIsStopping || bIsDone))
{
StopNow();
return;
}
if (bIsStopping)
{
UE_LOG(LogAudioMixer, Warning, TEXT("Restarting a source which was stopping. Stopping now."));
return;
}
AUDIO_MIXER_TRACE_CPUPROFILER_EVENT_SCOPE(AudioMixerSource::Play);
UE_CLOG(WaveInstance && Audio::MatchesLogFilter(*WaveInstance->GetName()), LogAudioTiming, Verbose,
TEXT("FMixerSource::Play, Name=%s, StartTime=%.2f bProcedural=%d, bIsSourceBus=%d, CachedRealTimeFirstBuffer=0x%p, MixerSourceBufferValid=%s, TickCount=%d, InitState=%s"),
*WaveInstance->GetName(), WaveInstance->StartTime, WaveInstance->WaveData->bProcedural, WaveInstance->WaveData->bIsSourceBus,
WaveInstance->WaveData->CachedRealtimeFirstBuffer,ToCStr(LexToString(MixerSourceBuffer.IsValid())), TickCount, ToCStr(LexToString(InitializationState)));
// It's possible if Pause and Play are called while a sound is async initializing. In this case
// we'll just not actually play the source here. Instead we'll call play when the sound finishes loading.
if (MixerSourceVoice && InitializationState == EMixerSourceInitializationState::Initialized)
{
UE_CLOG(WaveInstance && Audio::MatchesLogFilter(*WaveInstance->GetName()), LogAudioTiming, Verbose,
TEXT("FMixerSourceVoice::Play, Name=%s, StartTime=%.2f bProcedural=%d, bIsSourceBus=%d, CachedRealTimeFirstBuffer=0x%p, MixerSourceBufferValid=%s, TickCount=%d, InitState=%s"),
*WaveInstance->GetName(), WaveInstance->StartTime, WaveInstance->WaveData->bProcedural, WaveInstance->WaveData->bIsSourceBus,
WaveInstance->WaveData->CachedRealtimeFirstBuffer,ToCStr(LexToString(MixerSourceBuffer.IsValid())), TickCount, ToCStr(LexToString(InitializationState)));
MixerSourceVoice->Play();
#if UE_AUDIO_PROFILERTRACE_ENABLED
const bool bChannelEnabled = UE_TRACE_CHANNELEXPR_IS_ENABLED(AudioMixerChannel);
if (bChannelEnabled && WaveInstance)
{
if (const FActiveSound* ActiveSound = WaveInstance->ActiveSound)
{
int32 TraceSourceId = INDEX_NONE;
if (MixerSourceVoice)
{
TraceSourceId = MixerSourceVoice->GetSourceId();
}
UE_TRACE_LOG(Audio, MixerSourceStart, AudioMixerChannel)
<< MixerSourceStart.DeviceId(MixerDevice->DeviceID)
<< MixerSourceStart.Timestamp(FPlatformTime::Cycles64())
<< MixerSourceStart.PlayOrder(WaveInstance->GetPlayOrder())
<< MixerSourceStart.SourceId(TraceSourceId)
<< MixerSourceStart.ComponentId(ActiveSound->GetAudioComponentID())
<< MixerSourceStart.Name(*WaveInstance->WaveData->GetPathName());
}
}
#endif // UE_AUDIO_PROFILERTRACE_ENABLED
}
bIsStopping = false;
Paused = false;
Playing = true;
bLoopCallback = false;
bIsDone = false;
}
void FMixerSource::Stop()
{
LLM_SCOPE(ELLMTag::AudioMixer);
if (InitializationState == EMixerSourceInitializationState::NotInitialized)
{
return;
}
if (!MixerSourceVoice)
{
StopNow();
return;
}
USoundWave* SoundWave = WaveInstance ? WaveInstance->WaveData : nullptr;
// If MarkAsGarbage() was called, SoundWave can be null
if (!SoundWave)
{
StopNow();
return;
}
// Stop procedural sounds immediately that don't require fade
if (SoundWave->bProcedural && !SoundWave->bRequiresStopFade)
{
StopNow();
return;
}
if (bIsDone)
{
StopNow();
return;
}
if (Playing && !bIsStoppingVoicesEnabled)
{
StopNow();
return;
}
// Otherwise, we need to do a quick fade-out of the sound and put the state
// of the sound into "stopping" mode. This prevents this source from
// being put into the "free" pool and prevents the source from freeing its resources
// until the sound has finished naturally (i.e. faded all the way out)
// Let the wave instance know it's stopping
if (!bIsStopping)
{
WaveInstance->SetStopping(true);
MixerSourceVoice->StopFade(AudioMixerSourceFadeMinCVar);
bIsStopping = true;
Paused = false;
}
}
void FMixerSource::StopNow()
{
LLM_SCOPE(ELLMTag::AudioMixer);
// Immediately stop the sound source
InitializationState = EMixerSourceInitializationState::NotInitialized;
bIsStopping = false;
if (WaveInstance)
{
if (MixerSourceVoice && Playing)
{
#if UE_AUDIO_PROFILERTRACE_ENABLED
const bool bChannelEnabled = UE_TRACE_CHANNELEXPR_IS_ENABLED(AudioMixerChannel);
if (bChannelEnabled)
{
int32 TraceSourceId = INDEX_NONE;
if (MixerSourceVoice)
{
TraceSourceId = MixerSourceVoice->GetSourceId();
}
UE_TRACE_LOG(Audio, MixerSourceStop, AudioMixerChannel)
<< MixerSourceStop.DeviceId(MixerDevice->DeviceID)
<< MixerSourceStop.Timestamp(FPlatformTime::Cycles64())
<< MixerSourceStop.PlayOrder(WaveInstance->GetPlayOrder());
}
#endif // UE_AUDIO_PROFILERTRACE_ENABLED
MixerSourceVoice->Stop();
}
Paused = false;
Playing = false;
FreeResources();
}
FSoundSource::Stop();
}
void FMixerSource::Pause()
{
if (!WaveInstance)
{
return;
}
if (bIsStopping)
{
return;
}
if (MixerSourceVoice)
{
MixerSourceVoice->Pause();
}
Paused = true;
}
bool FMixerSource::IsFinished()
{
// A paused source is not finished.
if (Paused)
{
return false;
}
if (InitializationState == EMixerSourceInitializationState::NotInitialized)
{
return true;
}
if (InitializationState == EMixerSourceInitializationState::Initializing)
{
return false;
}
if (WaveInstance && MixerSourceVoice)
{
if (bIsDone && bIsEffectTailsDone)
{
WaveInstance->NotifyFinished();
bIsStopping = false;
return true;
}
else if (bLoopCallback && WaveInstance->LoopingMode == LOOP_WithNotification)
{
WaveInstance->NotifyFinished();
bLoopCallback = false;
}
return false;
}
return true;
}
float FMixerSource::GetPlaybackPercent() const
{
if (InitializationState != EMixerSourceInitializationState::Initialized)
{
return PreviousPlaybackPercent;
}
if (MixerSourceVoice && NumTotalFrames > 0)
{
int64 NumFrames = StartFrame + MixerSourceVoice->GetNumFramesPlayed();
AUDIO_MIXER_CHECK(NumTotalFrames > 0);
PreviousPlaybackPercent = (float)NumFrames / NumTotalFrames;
if (WaveInstance->LoopingMode == LOOP_Never)
{
PreviousPlaybackPercent = FMath::Min(PreviousPlaybackPercent, 1.0f);
}
return PreviousPlaybackPercent;
}
else
{
// If we don't have any frames, that means it's a procedural sound wave, which means
// that we're never going to have a playback percentage.
return 1.0f;
}
}
int64 FMixerSource::GetNumFramesPlayed() const
{
if (InitializationState == EMixerSourceInitializationState::Initialized && MixerSourceVoice != nullptr)
{
return MixerSourceVoice->GetNumFramesPlayed();
}
return 0;
}
float FMixerSource::GetEnvelopeValue() const
{
if (MixerSourceVoice)
{
return MixerSourceVoice->GetEnvelopeValue();
}
return 0.0f;
}
float FMixerSource::GetRelativeRenderCost() const
{
if (MixerSourceVoice)
{
return MixerSourceVoice->GetRelativeRenderCost();
}
return 1.0f;
}
void FMixerSource::OnBeginGenerate()
{
}
void FMixerSource::OnDone()
{
bIsDone = true;
}
void FMixerSource::OnEffectTailsDone()
{
bIsEffectTailsDone = true;
}
void FMixerSource::FreeResources()
{
LLM_SCOPE(ELLMTag::AudioMixer);
if (MixerBuffer)
{
MixerBuffer->EnsureHeaderParseTaskFinished();
}
check(!bIsStopping);
check(!Playing);
if (AudioLink.IsValid())
{
AudioLink.Reset();
}
// Make a new pending release data ptr to pass off release data
if (MixerSourceVoice)
{
// Release the source using the propagation interface
if (AudioDevice->SourceDataOverridePluginInterface)
{
uint32 SourceId = MixerSourceVoice->GetSourceId();
AudioDevice->SourceDataOverridePluginInterface->OnReleaseSource(SourceId);
}
// We're now "releasing" so don't recycle this voice until we get notified that the source has finished
bIsReleasing = true;
// This will trigger FMixerSource::OnRelease from audio render thread.
MixerSourceVoice->Release();
MixerSourceVoice = nullptr;
}
MixerSourceBuffer.Reset();
bLoopCallback = false;
NumTotalFrames = 0;
if (MixerBuffer)
{
EBufferType::Type BufferType = MixerBuffer->GetType();
if (BufferType == EBufferType::PCMRealTime || BufferType == EBufferType::Streaming)
{
delete MixerBuffer;
}
MixerBuffer = nullptr;
}
// Reset the source's channel maps
FRWScopeLock Lock(ChannelMapLock, SLT_Write);
ChannelMap.Reset();
InitializationState = EMixerSourceInitializationState::NotInitialized;
}
void FMixerSource::UpdatePitch()
{
AUDIO_MIXER_CHECK(MixerBuffer);
check(WaveInstance);
FActiveSound* ActiveSound = WaveInstance->ActiveSound;
check(ActiveSound);
Pitch = WaveInstance->GetPitch();
// Don't apply global pitch scale to UI sounds
if (!WaveInstance->bIsUISound)
{
Pitch *= AudioDevice->GetGlobalPitchScale().GetValue();
}
Pitch = AudioDevice->ClampPitch(Pitch);
// Scale the pitch by the ratio of the audio buffer sample rate and the actual sample rate of the hardware
if (MixerBuffer)
{
const float MixerBufferSampleRate = MixerBuffer->GetSampleRate();
const float AudioDeviceSampleRate = AudioDevice->GetSampleRate();
Pitch *= MixerBufferSampleRate / AudioDeviceSampleRate;
MixerSourceVoice->SetPitch(Pitch);
}
USoundWave* WaveData = WaveInstance->WaveData;
check(WaveData);
const float ModPitchBase = ModulationUtils::GetRoutedPitch(*WaveInstance, *WaveData, *ActiveSound);
MixerSourceVoice->SetModPitch(ModPitchBase);
}
float FMixerSource::GetInheritedSubmixVolumeModulation() const
{
if (!MixerDevice)
{
return 1.0f;
}
FAudioDevice::FAudioSpatializationInterfaceInfo SpatializationInfo = MixerDevice->GetCurrentSpatializationPluginInterfaceInfo();
// We only hit this condition if, while the sound is playing, the spatializer changes from an external send to a non-external one.
// If that happens, the submix will catch all modulation so this function's logic is not needed.
if (!SpatializationInfo.bSpatializationIsExternalSend)
{
return 1.0f;
}
// if there is a return submix, we need to figure out where to stop manually attenuating
// Because the submix will modulate itself later
// Since the graph has tree-like structure, we can create a list of the return submix's ancestors
// to use while traversing the other submix's ancestors
TArray<uint32> ReturnSubmixAncestors;
if (SpatializationInfo.bReturnsToSubmixGraph)
{
if (MixerDevice && MixerDevice->ReverbPluginInterface)
{
USoundSubmix* ReturnSubmix = MixerDevice->ReverbPluginInterface->GetSubmix();
if (ReturnSubmix)
{
FMixerSubmixWeakPtr CurrReturnSubmixWeakPtr = MixerDevice->GetSubmixInstance(ReturnSubmix);
FMixerSubmixPtr CurrReturnSubmixPtr = CurrReturnSubmixWeakPtr.Pin();
while (CurrReturnSubmixPtr && CurrReturnSubmixPtr->IsValid())
{
ReturnSubmixAncestors.Add(CurrReturnSubmixPtr->GetId());
CurrReturnSubmixWeakPtr = CurrReturnSubmixPtr->GetParent();
CurrReturnSubmixPtr = CurrReturnSubmixWeakPtr.Pin();
}
}
}
}
float SubmixModVolume = 1.0f;
FMixerSubmixWeakPtr CurrSubmixWeakPtr = MixerDevice->GetSubmixInstance(WaveInstance->SoundSubmix);
FMixerSubmixPtr CurrSubmixPtr = CurrSubmixWeakPtr.Pin();
// Check the submix and all its parents in the graph for active modulation
while (CurrSubmixPtr && CurrSubmixPtr->IsValid())
{
// Matching ID means the external spatializer has returned to the submix graph at this point,
// so we no longer need to manually apply volume modulation
if (SpatializationInfo.bReturnsToSubmixGraph && ReturnSubmixAncestors.Contains(CurrSubmixPtr->GetId()))
{
break;
}
FModulationDestination* SubmixOutVolDest = CurrSubmixPtr->GetOutputVolumeDestination();
FModulationDestination* SubmixWetVolDest = CurrSubmixPtr->GetWetVolumeDestination();
if (SubmixOutVolDest)
{
SubmixModVolume *= SubmixOutVolDest->GetValue();
}
if (SubmixWetVolDest)
{
SubmixModVolume *= SubmixWetVolDest->GetValue();
}
CurrSubmixWeakPtr = CurrSubmixPtr->GetParent();
CurrSubmixPtr = CurrSubmixWeakPtr.Pin();
}
return SubmixModVolume;
}
void FMixerSource::UpdateVolume()
{
// TODO: investigate if occlusion should be split from raw distance attenuation
MixerSourceVoice->SetDistanceAttenuation(WaveInstance->GetDistanceAndOcclusionAttenuation());
float CurrentVolume = 0.0f;
if (!AudioDevice->IsAudioDeviceMuted())
{
// 1. Apply device gain stage(s)
CurrentVolume = WaveInstance->ActiveSound->bIsPreviewSound ? 1.0f : AudioDevice->GetPrimaryVolume();
CurrentVolume *= AudioDevice->GetPlatformAudioHeadroom();
// 2. Apply instance gain stage(s)
CurrentVolume *= WaveInstance->GetVolume();
CurrentVolume *= WaveInstance->GetDynamicVolume();
// 3. Submix Volume Modulation (this only happens if the asset is binaural and we're sending to an external submix)
if (bBypassingSubmixModulation)
{
CurrentVolume *= GetInheritedSubmixVolumeModulation();
}
// 4. Apply editor gain stage(s)
CurrentVolume = FMath::Clamp<float>(GetDebugVolume(CurrentVolume), 0.0f, MAX_VOLUME);
FActiveSound* ActiveSound = WaveInstance->ActiveSound;
check(ActiveSound);
USoundWave* WaveData = WaveInstance->WaveData;
check(WaveData);
const float ModVolumeBase = ModulationUtils::GetRoutedVolume(*WaveInstance, *WaveData, *ActiveSound);
MixerSourceVoice->SetModVolume(ModVolumeBase);
}
MixerSourceVoice->SetVolume(CurrentVolume);
}
void FMixerSource::UpdateSpatialization()
{
FQuat LastEmitterWorldRotation = SpatializationParams.EmitterWorldRotation;
SpatializationParams = GetSpatializationParams();
SpatializationParams.LastEmitterWorldRotation = LastEmitterWorldRotation;
if (WaveInstance->GetUseSpatialization() || WaveInstance->bIsAmbisonics)
{
MixerSourceVoice->SetSpatializationParams(SpatializationParams);
}
}
void FMixerSource::UpdateSubmixSendLevels(const FSoundSubmixSendInfoBase& InSendInfo, const EMixerSourceSubmixSendStage InSendStage, TSet<FMixerSubmixWeakPtr>& OutTouchedSubmixes)
{
if (InSendInfo.SoundSubmix != nullptr)
{
const FMixerSubmixWeakPtr SubmixInstance = MixerDevice->GetSubmixInstance(InSendInfo.SoundSubmix);
float SendLevel = 1.0f;
// Add it to our touched submix list.
OutTouchedSubmixes.Add(SubmixInstance);
// calculate send level based on distance if that method is enabled
if (!WaveInstance->bEnableSubmixSends)
{
SendLevel = 0.0f;
}
else if (InSendInfo.SendLevelControlMethod == ESendLevelControlMethod::Manual)
{
if (InSendInfo.DisableManualSendClamp)
{
SendLevel = InSendInfo.SendLevel;
}
else
{
SendLevel = FMath::Clamp(InSendInfo.SendLevel, 0.0f, 1.0f);
}
}
else
{
// The alpha value is determined identically between manual and custom curve methods
const FVector2D SendRadialRange = { InSendInfo.MinSendDistance, InSendInfo.MaxSendDistance};
const FVector2D SendLevelRange = { InSendInfo.MinSendLevel, InSendInfo.MaxSendLevel };
const float Denom = FMath::Max(SendRadialRange.Y - SendRadialRange.X, 1.0f);
const float Alpha = FMath::Clamp((WaveInstance->ListenerToSoundDistance - SendRadialRange.X) / Denom, 0.0f, 1.0f);
if (InSendInfo.SendLevelControlMethod == ESendLevelControlMethod::Linear)
{
SendLevel = FMath::Clamp(FMath::Lerp(SendLevelRange.X, SendLevelRange.Y, Alpha), 0.0f, 1.0f);
}
else // use curve
{
SendLevel = FMath::Clamp(InSendInfo.CustomSendLevelCurve.GetRichCurveConst()->Eval(Alpha), 0.0f, 1.0f);
}
}
// set the level and stage for this send
MixerSourceVoice->SetSubmixSendInfo(SubmixInstance, SendLevel, InSendStage);
}
}
void FMixerSource::UpdateEffects()
{
// Update the default LPF filter frequency
SetFilterFrequency();
MixerSourceVoice->SetLPFFrequency(LPFFrequency);
MixerSourceVoice->SetHPFFrequency(HPFFrequency);
check(WaveInstance);
FActiveSound* ActiveSound = WaveInstance->ActiveSound;
check(ActiveSound);
USoundWave* WaveData = WaveInstance->WaveData;
check(WaveData);
float ModHighpassBase = ModulationUtils::GetRoutedHighpass(*WaveInstance, *WaveData, *ActiveSound);
MixerSourceVoice->SetModHPFFrequency(ModHighpassBase);
float ModLowpassBase = ModulationUtils::GetRoutedLowpass(*WaveInstance, *WaveData, *ActiveSound);
MixerSourceVoice->SetModLPFFrequency(ModLowpassBase);
// If reverb is applied, figure out how of the source to "send" to the reverb.
if (WaveInstance->bReverb)
{
// Send the source audio to the reverb plugin if enabled
if (UseReverbPlugin() && AudioDevice->ReverbPluginInterface)
{
check(MixerDevice);
FMixerSubmixPtr ReverbPluginSubmixPtr = MixerDevice->GetSubmixInstance(AudioDevice->ReverbPluginInterface->GetSubmix()).Pin();
if (ReverbPluginSubmixPtr.IsValid())
{
MixerSourceVoice->SetSubmixSendInfo(ReverbPluginSubmixPtr, WaveInstance->ReverbSendLevel);
}
}
// Send the source audio to the master reverb
MixerSourceVoice->SetSubmixSendInfo(MixerDevice->GetMasterReverbSubmix(), WaveInstance->ReverbSendLevel);
}
// Safely track if the submix has changed between updates.
bool bSubmixHasChanged = false;
TObjectKey<USoundSubmixBase> SubmixKey(WaveInstance->SoundSubmix);
if (SubmixKey != PrevousSubmix )
{
bSubmixHasChanged = true;
}
// This will reattempt to resolve a submix each update if there's a valid input
if ((!WaveInstance->SoundSubmix && PreviousSubmixResolved.IsValid()) ||
(WaveInstance->SoundSubmix && !PreviousSubmixResolved.IsValid()) )
{
bSubmixHasChanged = true;
}
//Check whether the base submix send has been enabled or disabled since the last update
//Or if the submix has now been registered with the world.
if (WaveInstance->bEnableBaseSubmix != bPreviousBaseSubmixEnablement || bSubmixHasChanged)
{
// set the level for this send
FMixerSubmixWeakPtr SubmixPtr;
if (WaveInstance->SoundSubmix)
{
SubmixPtr = MixerDevice->GetSubmixInstance(WaveInstance->SoundSubmix);
}
else if (!WaveInstance->bIsDynamic) // Dynamic submixes don't auto connect.
{
SubmixPtr = MixerDevice->GetBaseDefaultSubmix(); // This will try base default and fall back to master if that fails.
}
MixerSourceVoice->SetSubmixSendInfo(SubmixPtr, WaveInstance->bEnableBaseSubmix);
bPreviousBaseSubmixEnablement = WaveInstance->bEnableBaseSubmix;
PreviousSubmixResolved = SubmixPtr;
PrevousSubmix = SubmixKey;
}
// We clear sends that aren't used between updates. So tally up the ones that are used.
// Including the submix itself.
// It's okay to use "previous" submix here as it's set above or from a previous setting.
TSet<FMixerSubmixWeakPtr> TouchedSubmixes;
TouchedSubmixes.Add(PreviousSubmixResolved);
// Attenuation Submix Sends. (these come from Attenuation assets).
// These are largely identical to SoundSubmix Sends, but don't specify a send stage, so we pass one here.
for (const FAttenuationSubmixSendSettings& SendSettings : WaveInstance->AttenuationSubmixSends)
{
UpdateSubmixSendLevels(SendSettings, EMixerSourceSubmixSendStage::PostDistanceAttenuation, TouchedSubmixes);
}
// Sound submix Sends. (these come from SoundBase derived assets).
for (FSoundSubmixSendInfo& SendInfo : WaveInstance->SoundSubmixSends)
{
UpdateSubmixSendLevels(SendInfo, MixerSourcePrivate::SubmixSendStageToMixerSourceSubmixSendStage(SendInfo.SendStage), TouchedSubmixes);
}
// Anything we haven't touched this update we should now clear.
const TSet<FMixerSubmixWeakPtr> ToClear = PreviousSubmixSends.Difference(TouchedSubmixes);
PreviousSubmixSends = TouchedSubmixes;
// Clear sends that aren't touched.
for (FMixerSubmixWeakPtr i : ToClear)
{
MixerSourceVoice->ClearSubmixSendInfo(i);
}
MixerSourceVoice->SetEnablement(WaveInstance->bEnableBusSends, WaveInstance->bEnableBaseSubmix, WaveInstance->bEnableSubmixSends);
#if WITH_EDITOR
// The following can spam to the command queue. But is mostly here so that the editor live edits are immedately heard
// For anything less than editor this is perf waste, so predicate this only to be run in editor.
MixerSourceVoice->SetSourceBufferListener(WaveInstance->SourceBufferListener, WaveInstance->bShouldSourceBufferListenerZeroBuffer);
#endif // WITH_EDITOR
}
void FMixerSource::UpdateModulation()
{
check(WaveInstance);
FActiveSound* ActiveSound = WaveInstance->ActiveSound;
check(ActiveSound);
if (ActiveSound->bModulationRoutingUpdated)
{
if (WaveInstance->WaveData)
{
FSoundModulationDefaultRoutingSettings UpdatedRouting = ModulationUtils::UpdateRoutedModulation(*WaveInstance, *(WaveInstance->WaveData), ActiveSound);
MixerSourceVoice->SetModulationRouting(UpdatedRouting);
}
else
{
MixerSourceVoice->SetModulationRouting(ActiveSound->ModulationRouting);
}
}
ActiveSound->bModulationRoutingUpdated = false;
// Query a modulation value for the active sound to use during concurrency evaluation
const float SourceModVolume = MixerSourceVoice->GetVolumeModulationValue();
ActiveSound->MaxSourceModulationValue = FMath::Max(SourceModVolume, ActiveSound->MaxSourceModulationValue);
}
void FMixerSource::UpdateSourceBusSends()
{
// 1) loop through all bus sends
// 2) check for any bus sends that are set to update non-manually
// 3) Cache previous send level and only do update if it's changed in any significant amount
SetupBusData();
FActiveSound* ActiveSound = WaveInstance->ActiveSound;
check(ActiveSound);
// Check if the user actively called a function that alters bus sends since the last update
bool bHasNewBusSends = ActiveSound->HasNewBusSends();
if (!bSendingAudioToBuses && !bHasNewBusSends && !DynamicBusSendInfos.Num())
{
return;
}
if (bHasNewBusSends)
{
TArray<TTuple<EBusSendType, FSoundSourceBusSendInfo>> NewBusSends = ActiveSound->GetNewBusSends();
for (TTuple<EBusSendType, FSoundSourceBusSendInfo>& NewSend : NewBusSends)
{
if (NewSend.Value.SoundSourceBus)
{
MixerSourceVoice->SetAudioBusSendInfo(NewSend.Key, NewSend.Value.SoundSourceBus->GetUniqueID(), NewSend.Value.SendLevel);
bSendingAudioToBuses = true;
}
if (NewSend.Value.AudioBus)
{
MixerSourceVoice->SetAudioBusSendInfo(NewSend.Key, NewSend.Value.AudioBus->GetUniqueID(), NewSend.Value.SendLevel);
bSendingAudioToBuses = true;
}
}
ActiveSound->ResetNewBusSends();
}
// If this source is sending its audio to a bus, we need to check if it needs to be updated
for (FDynamicBusSendInfo& DynamicBusSendInfo : DynamicBusSendInfos)
{
float SendLevel = 0.0f;
if (DynamicBusSendInfo.BusSendLevelControlMethod == ESourceBusSendLevelControlMethod::Manual)
{
SendLevel = FMath::Clamp(DynamicBusSendInfo.SendLevel, 0.0f, 1.0f);
}
else
{
// The alpha value is determined identically between linear and custom curve methods
const FVector2D SendRadialRange = { DynamicBusSendInfo.MinSendDistance, DynamicBusSendInfo.MaxSendDistance };
const FVector2D SendLevelRange = { DynamicBusSendInfo.MinSendLevel, DynamicBusSendInfo.MaxSendLevel };
const float Denom = FMath::Max(SendRadialRange.Y - SendRadialRange.X, 1.0f);
const float Alpha = FMath::Clamp((WaveInstance->ListenerToSoundDistance - SendRadialRange.X) / Denom, 0.0f, 1.0f);
if (DynamicBusSendInfo.BusSendLevelControlMethod == ESourceBusSendLevelControlMethod::Linear)
{
SendLevel = FMath::Clamp(FMath::Lerp(SendLevelRange.X, SendLevelRange.Y, Alpha), 0.0f, 1.0f);
}
else // use curve
{
SendLevel = FMath::Clamp(DynamicBusSendInfo.CustomSendLevelCurve.GetRichCurveConst()->Eval(Alpha), 0.0f, 1.0f);
}
}
// If the send level changed, then we need to send an update to the audio render thread
const bool bSendLevelChanged = !FMath::IsNearlyEqual(SendLevel, DynamicBusSendInfo.SendLevel);
const bool bBusEnablementChanged = bPreviousBusEnablement != WaveInstance->bEnableBusSends;
if (bSendLevelChanged || bBusEnablementChanged)
{
DynamicBusSendInfo.SendLevel = SendLevel;
DynamicBusSendInfo.bIsInit = false;
MixerSourceVoice->SetAudioBusSendInfo(DynamicBusSendInfo.BusSendType, DynamicBusSendInfo.BusId, SendLevel);
bPreviousBusEnablement = WaveInstance->bEnableBusSends;
}
}
}
void FMixerSource::UpdateChannelMaps()
{
SetLFEBleed();
int32 NumOutputDeviceChannels = MixerDevice->GetNumDeviceChannels();
const FAudioPlatformDeviceInfo& DeviceInfo = MixerDevice->GetPlatformDeviceInfo();
// Compute a new speaker map for each possible output channel mapping for the source
bool bShouldSetMap = false;
{
FRWScopeLock Lock(ChannelMapLock, SLT_Write);
bShouldSetMap = ComputeChannelMap(GetNumChannels(), ChannelMap);
}
if(bShouldSetMap)
{
FRWScopeLock Lock(ChannelMapLock, SLT_ReadOnly);
MixerSourceVoice->SetChannelMap(NumChannels, ChannelMap, bIs3D, WaveInstance->bCenterChannelOnly);
}
bPrevAllowedSpatializationSetting = IsSpatializationCVarEnabled();
}
void FMixerSource::UpdateRelativeRenderCost()
{
if (MixerSourceVoice)
{
const float RelativeRenderCost = MixerSourceVoice->GetRelativeRenderCost();
check(WaveInstance);
WaveInstance->SetRelativeRenderCost(RelativeRenderCost);
#if ENABLE_AUDIO_DEBUG
if (DebugInfo.IsValid())
{
FScopeLock DebugInfoLock(&DebugInfo->CS);
DebugInfo->RelativeRenderCost = RelativeRenderCost;
}
#endif // if ENABLE_AUDIO_DEBUG
}
}
#if ENABLE_AUDIO_DEBUG
void FMixerSource::UpdateCPUCoreUtilization()
{
if (MixerSourceVoice)
{
if (DebugInfo.IsValid())
{
FScopeLock DebugInfoLock(&DebugInfo->CS);
DebugInfo->CPUCoreUtilization = MixerSourceVoice->GetCPUCoreUtilization();
}
}
}
#endif // if ENABLE_AUDIO_DEBUG
#if UE_AUDIO_PROFILERTRACE_ENABLED
void FMixerSource::OnTraceStarted(FTraceAuxiliary::EConnectionType TraceType, const FString& TraceDestination)
{
// Make sure we send mixer source data to Audio Insights if it's opened in the middle of a PIE session
const bool bChannelEnabled = UE_TRACE_CHANNELEXPR_IS_ENABLED(AudioMixerChannel);
if (bChannelEnabled && WaveInstance)
{
if (const FActiveSound* ActiveSound = WaveInstance->ActiveSound;
ActiveSound && ActiveSound->IsPlayingAudio())
{
int32 TraceSourceId = INDEX_NONE;
if (MixerSourceVoice)
{
TraceSourceId = MixerSourceVoice->GetSourceId();
}
UE_TRACE_LOG(Audio, MixerSourceStart, AudioMixerChannel)
<< MixerSourceStart.DeviceId(MixerDevice->DeviceID)
<< MixerSourceStart.Timestamp(FPlatformTime::Cycles64())
<< MixerSourceStart.PlayOrder(WaveInstance->GetPlayOrder())
<< MixerSourceStart.SourceId(TraceSourceId)
<< MixerSourceStart.ComponentId(ActiveSound->GetAudioComponentID())
<< MixerSourceStart.Name(*WaveInstance->WaveData->GetPathName());
}
}
}
#endif // UE_AUDIO_PROFILERTRACE_ENABLED
bool FMixerSource::ComputeMonoChannelMap(Audio::FAlignedFloatBuffer& OutChannelMap)
{
if (IsUsingObjectBasedSpatialization())
{
if (WaveInstance->SpatializationMethod != ESoundSpatializationAlgorithm::SPATIALIZATION_HRTF && !bEditorWarnedChangedSpatialization)
{
bEditorWarnedChangedSpatialization = true;
UE_LOG(LogAudioMixer, Warning, TEXT("Changing the spatialization method on a playing sound is not supported (WaveInstance: %s)"), *WaveInstance->WaveData->GetFullName());
}
// Treat the source as if it is a 2D stereo source:
return ComputeStereoChannelMap(OutChannelMap);
}
else if (WaveInstance->GetUseSpatialization() && (!FMath::IsNearlyEqual(WaveInstance->AbsoluteAzimuth, PreviousAzimuth, 0.01f) || MixerSourceVoice->NeedsSpeakerMap()))
{
// Don't need to compute the source channel map if the absolute azimuth hasn't changed much
PreviousAzimuth = WaveInstance->AbsoluteAzimuth;
OutChannelMap.Reset();
int32 NumOutputChannels = MixerDevice->GetNumDeviceChannels();
if (WaveInstance->NonSpatializedRadiusMode == ENonSpatializedRadiusSpeakerMapMode::OmniDirectional)
{
float DefaultOmniAmount = 1.0f / NumOutputChannels;
MixerDevice->Get3DChannelMap(NumOutputChannels, WaveInstance, WaveInstance->AbsoluteAzimuth, SpatializationParams.NonSpatializedAmount, nullptr, DefaultOmniAmount, OutChannelMap);
}
else if (WaveInstance->NonSpatializedRadiusMode == ENonSpatializedRadiusSpeakerMapMode::Direct2D)
{
// Create some omni maps for left and right channels, note we're
// taking into account mono upmix method
auto CreateOmniMap = [this]() -> TMap<EAudioMixerChannel::Type, float>
{
EMonoChannelUpmixMethod MonoUpmixMethod = MixerDevice->GetMonoChannelUpmixMethod();
TMap<EAudioMixerChannel::Type, float> OmniMap;
if (MonoUpmixMethod == EMonoChannelUpmixMethod::FullVolume)
{
OmniMap.Add(EAudioMixerChannel::FrontLeft, Audio::MonoUpmixFullVolume);
OmniMap.Add(EAudioMixerChannel::FrontRight, Audio::MonoUpmixFullVolume);
}
else if (MonoUpmixMethod == EMonoChannelUpmixMethod::EqualPower)
{
OmniMap.Add(EAudioMixerChannel::FrontLeft, Audio::MonoUpmixEqualPower);
OmniMap.Add(EAudioMixerChannel::FrontRight, Audio::MonoUpmixEqualPower);
}
else
{
check(MonoUpmixMethod == EMonoChannelUpmixMethod::Linear);
OmniMap.Add(EAudioMixerChannel::FrontLeft, Audio::MonoUpmixLinear);
OmniMap.Add(EAudioMixerChannel::FrontRight, Audio::MonoUpmixLinear);
}
return OmniMap;
};
static const TMap<EAudioMixerChannel::Type, float> OmniMap = CreateOmniMap();
MixerDevice->Get3DChannelMap(NumOutputChannels, WaveInstance, WaveInstance->AbsoluteAzimuth, SpatializationParams.NonSpatializedAmount, &OmniMap, 0.0f, OutChannelMap);
}
else if (WaveInstance->NonSpatializedRadiusMode == ENonSpatializedRadiusSpeakerMapMode::Surround2D)
{
// Create some omni maps for left and right channels, note we're
// taking into account mono upmix method
auto CreateOmniMap = [this, &NumOutputChannels]() -> TMap<EAudioMixerChannel::Type, float>
{
EMonoChannelUpmixMethod MonoUpmixMethod = MixerDevice->GetMonoChannelUpmixMethod();
TMap<EAudioMixerChannel::Type, float> OmniMap;
if (MonoUpmixMethod == EMonoChannelUpmixMethod::FullVolume)
{
OmniMap.Add(EAudioMixerChannel::FrontLeft, Audio::MonoUpmixFullVolume);
OmniMap.Add(EAudioMixerChannel::FrontRight, Audio::MonoUpmixFullVolume);
if (NumOutputChannels == 8)
{
OmniMap.Add(EAudioMixerChannel::BackLeft, Audio::MonoUpmixFullVolume);
OmniMap.Add(EAudioMixerChannel::BackRight, Audio::MonoUpmixFullVolume);
}
else if (NumOutputChannels == 6)
{
OmniMap.Add(EAudioMixerChannel::SideLeft, Audio::MonoUpmixFullVolume);
OmniMap.Add(EAudioMixerChannel::SideRight, Audio::MonoUpmixFullVolume);
}
}
else if (MonoUpmixMethod == EMonoChannelUpmixMethod::EqualPower)
{
OmniMap.Add(EAudioMixerChannel::FrontLeft, Audio::MonoUpmixEqualPower);
OmniMap.Add(EAudioMixerChannel::FrontRight, Audio::MonoUpmixEqualPower);
if (NumOutputChannels == 8)
{
OmniMap.Add(EAudioMixerChannel::BackLeft, Audio::MonoUpmixEqualPower);
OmniMap.Add(EAudioMixerChannel::BackRight, Audio::MonoUpmixEqualPower);
}
else
{
OmniMap.Add(EAudioMixerChannel::SideLeft, Audio::MonoUpmixEqualPower);
OmniMap.Add(EAudioMixerChannel::SideRight, Audio::MonoUpmixEqualPower);
}
}
else
{
check(MonoUpmixMethod == EMonoChannelUpmixMethod::Linear);
OmniMap.Add(EAudioMixerChannel::FrontLeft, Audio::MonoUpmixLinear);
OmniMap.Add(EAudioMixerChannel::FrontRight, Audio::MonoUpmixLinear);
if (NumOutputChannels == 8)
{
OmniMap.Add(EAudioMixerChannel::BackLeft, Audio::MonoUpmixLinear);
OmniMap.Add(EAudioMixerChannel::BackRight, Audio::MonoUpmixLinear);
}
else if (NumOutputChannels == 6)
{
OmniMap.Add(EAudioMixerChannel::SideLeft, Audio::MonoUpmixLinear);
OmniMap.Add(EAudioMixerChannel::SideRight, Audio::MonoUpmixLinear);
}
}
return OmniMap;
};
static const TMap<EAudioMixerChannel::Type, float> OmniMap = CreateOmniMap();
MixerDevice->Get3DChannelMap(NumOutputChannels, WaveInstance, WaveInstance->AbsoluteAzimuth, SpatializationParams.NonSpatializedAmount, &OmniMap, 0.0f, OutChannelMap);
}
return true;
}
else if (!OutChannelMap.Num() || (IsSpatializationCVarEnabled() != bPrevAllowedSpatializationSetting))
{
// Only need to compute the 2D channel map once
MixerDevice->Get2DChannelMap(bIsVorbis, 1, WaveInstance->bCenterChannelOnly, OutChannelMap);
return true;
}
// Return false means the channel map hasn't changed
return false;
}
bool FMixerSource::ComputeStereoChannelMap(Audio::FAlignedFloatBuffer& OutChannelMap)
{
// Only recalculate positional data if the source has moved a significant amount:
if (WaveInstance->GetUseSpatialization() && (!FMath::IsNearlyEqual(WaveInstance->AbsoluteAzimuth, PreviousAzimuth, 0.01f) || MixerSourceVoice->NeedsSpeakerMap()))
{
// Make sure our stereo emitter positions are updated relative to the sound emitter position
if (GetNumChannels() == 2)
{
UpdateStereoEmitterPositions();
}
// Check whether voice is currently using
if (!IsUsingObjectBasedSpatialization())
{
float AzimuthOffset = 0.0f;
float LeftAzimuth = 90.0f;
float RightAzimuth = 270.0f;
const float DistanceToUse = UseListenerOverrideForSpreadCVar ? WaveInstance->ListenerToSoundDistance : WaveInstance->ListenerToSoundDistanceForPanning;
if (DistanceToUse > KINDA_SMALL_NUMBER)
{
AzimuthOffset = FMath::Atan(0.5f * WaveInstance->StereoSpread / DistanceToUse);
AzimuthOffset = FMath::RadiansToDegrees(AzimuthOffset);
LeftAzimuth = WaveInstance->AbsoluteAzimuth - AzimuthOffset;
if (LeftAzimuth < 0.0f)
{
LeftAzimuth += 360.0f;
}
RightAzimuth = WaveInstance->AbsoluteAzimuth + AzimuthOffset;
if (RightAzimuth > 360.0f)
{
RightAzimuth -= 360.0f;
}
}
// Reset the channel map, the stereo spatialization channel mapping calls below will append their mappings
OutChannelMap.Reset();
int32 NumOutputChannels = MixerDevice->GetNumDeviceChannels();
if (WaveInstance->NonSpatializedRadiusMode == ENonSpatializedRadiusSpeakerMapMode::OmniDirectional)
{
float DefaultOmniAmount = 1.0f / NumOutputChannels;
MixerDevice->Get3DChannelMap(NumOutputChannels, WaveInstance, LeftAzimuth, SpatializationParams.NonSpatializedAmount, nullptr, DefaultOmniAmount, OutChannelMap);
MixerDevice->Get3DChannelMap(NumOutputChannels, WaveInstance, RightAzimuth, SpatializationParams.NonSpatializedAmount, nullptr, DefaultOmniAmount, OutChannelMap);
}
else if (WaveInstance->NonSpatializedRadiusMode == ENonSpatializedRadiusSpeakerMapMode::Direct2D)
{
// Create some omni maps for left and right channels
auto CreateLeftOmniMap = []() -> TMap<EAudioMixerChannel::Type, float>
{
TMap<EAudioMixerChannel::Type, float> LeftOmniMap;
LeftOmniMap.Add(EAudioMixerChannel::FrontLeft, 1.0f);
return LeftOmniMap;
};
auto CreateRightOmniMap = []() -> TMap<EAudioMixerChannel::Type, float>
{
TMap<EAudioMixerChannel::Type, float> RightOmniMap;
RightOmniMap.Add(EAudioMixerChannel::FrontRight, 1.0f);
return RightOmniMap;
};
static const TMap<EAudioMixerChannel::Type, float> LeftOmniMap = CreateLeftOmniMap();
static const TMap<EAudioMixerChannel::Type, float> RightOmniMap = CreateRightOmniMap();
MixerDevice->Get3DChannelMap(NumOutputChannels, WaveInstance, LeftAzimuth, SpatializationParams.NonSpatializedAmount, &LeftOmniMap, 0.0f, OutChannelMap);
MixerDevice->Get3DChannelMap(NumOutputChannels, WaveInstance, RightAzimuth, SpatializationParams.NonSpatializedAmount, &RightOmniMap, 0.0f, OutChannelMap);
}
else
{
// If we are in 5.1, we need to use the side-channel speakers
// If we are outputting stereo, omni-blend to a 5.1 output. This will get downmixed to stereo as a fallback.
if (NumOutputChannels == 2 || NumOutputChannels == 6)
{
// Create some omni maps for left and right channels
auto CreateLeftOmniMap = []() -> TMap<EAudioMixerChannel::Type, float>
{
TMap<EAudioMixerChannel::Type, float> LeftOmniMap;
LeftOmniMap.Add(EAudioMixerChannel::FrontLeft, 1.0f);
LeftOmniMap.Add(EAudioMixerChannel::SideLeft, 1.0f);
return LeftOmniMap;
};
auto CreateRightOmniMap = []() -> TMap<EAudioMixerChannel::Type, float>
{
TMap<EAudioMixerChannel::Type, float> RightOmniMap;
RightOmniMap.Add(EAudioMixerChannel::FrontRight, 1.0f);
RightOmniMap.Add(EAudioMixerChannel::SideRight, 1.0f);
return RightOmniMap;
};
static const TMap<EAudioMixerChannel::Type, float> LeftOmniMap = CreateLeftOmniMap();
static const TMap<EAudioMixerChannel::Type, float> RightOmniMap = CreateRightOmniMap();
MixerDevice->Get3DChannelMap(NumOutputChannels, WaveInstance, LeftAzimuth, SpatializationParams.NonSpatializedAmount, &LeftOmniMap, 0.0f, OutChannelMap);
MixerDevice->Get3DChannelMap(NumOutputChannels, WaveInstance, RightAzimuth, SpatializationParams.NonSpatializedAmount, &RightOmniMap, 0.0f, OutChannelMap);
}
// If we are in 7.1 we need to use the back-channel speakers
else if (NumOutputChannels == 8)
{
// Create some omni maps for left and right channels
auto CreateLeftOmniMap = []() -> TMap<EAudioMixerChannel::Type, float>
{
TMap<EAudioMixerChannel::Type, float> LeftOmniMap;
LeftOmniMap.Add(EAudioMixerChannel::FrontLeft, 1.0f);
LeftOmniMap.Add(EAudioMixerChannel::BackLeft, 1.0f);
return LeftOmniMap;
};
auto CreateRightOmniMap = []() -> TMap<EAudioMixerChannel::Type, float>
{
TMap<EAudioMixerChannel::Type, float> RightOmniMap;
RightOmniMap.Add(EAudioMixerChannel::FrontRight, 1.0f);
RightOmniMap.Add(EAudioMixerChannel::BackRight, 1.0f);
return RightOmniMap;
};
static const TMap<EAudioMixerChannel::Type, float> LeftOmniMap = CreateLeftOmniMap();
static const TMap<EAudioMixerChannel::Type, float> RightOmniMap = CreateRightOmniMap();
MixerDevice->Get3DChannelMap(NumOutputChannels, WaveInstance, LeftAzimuth, SpatializationParams.NonSpatializedAmount, &LeftOmniMap, 0.0f, OutChannelMap);
MixerDevice->Get3DChannelMap(NumOutputChannels, WaveInstance, RightAzimuth, SpatializationParams.NonSpatializedAmount, &RightOmniMap, 0.0f, OutChannelMap);
}
}
return true;
}
}
if (!OutChannelMap.Num() || (IsSpatializationCVarEnabled() != bPrevAllowedSpatializationSetting))
{
MixerDevice->Get2DChannelMap(bIsVorbis, 2, WaveInstance->bCenterChannelOnly, OutChannelMap);
return true;
}
return false;
}
bool FMixerSource::ComputeChannelMap(const int32 NumSourceChannels, Audio::FAlignedFloatBuffer& OutChannelMap)
{
if (NumSourceChannels == 1)
{
return ComputeMonoChannelMap(OutChannelMap);
}
else if (NumSourceChannels == 2)
{
return ComputeStereoChannelMap(OutChannelMap);
}
else if (!OutChannelMap.Num())
{
MixerDevice->Get2DChannelMap(bIsVorbis, NumSourceChannels, WaveInstance->bCenterChannelOnly, OutChannelMap);
return true;
}
return false;
}
bool FMixerSource::UseObjectBasedSpatialization() const
{
return (GetNumChannels() <= MixerDevice->GetCurrentSpatializationPluginInterfaceInfo().MaxChannelsSupportedBySpatializationPlugin &&
AudioDevice->IsSpatializationPluginEnabled() &&
WaveInstance->SpatializationMethod == ESoundSpatializationAlgorithm::SPATIALIZATION_HRTF);
}
bool FMixerSource::IsUsingObjectBasedSpatialization() const
{
bool bIsUsingObjectBaseSpatialization = UseObjectBasedSpatialization();
if (MixerSourceVoice)
{
// If it is currently playing, check whether it actively uses HRTF spatializer.
// HRTF spatialization cannot be altered on currently playing source. So this handles
// the case where the source was initialized without HRTF spatialization before HRTF
// spatialization is enabled.
bool bDefaultIfNoSourceId = true;
bIsUsingObjectBaseSpatialization &= MixerSourceVoice->IsUsingHRTFSpatializer(bDefaultIfNoSourceId);
}
return bIsUsingObjectBaseSpatialization;
}
bool FMixerSource::UseSpatializationPlugin() const
{
return (GetNumChannels() <= MixerDevice->GetCurrentSpatializationPluginInterfaceInfo().MaxChannelsSupportedBySpatializationPlugin) &&
AudioDevice->IsSpatializationPluginEnabled() &&
WaveInstance->SpatializationPluginSettings != nullptr;
}
bool FMixerSource::UseOcclusionPlugin() const
{
return (GetNumChannels() == 1 || GetNumChannels() == 2) &&
AudioDevice->IsOcclusionPluginEnabled() &&
WaveInstance->OcclusionPluginSettings != nullptr;
}
bool FMixerSource::UseReverbPlugin() const
{
return (GetNumChannels() == 1 || GetNumChannels() == 2) &&
AudioDevice->IsReverbPluginEnabled() &&
WaveInstance->ReverbPluginSettings != nullptr;
}
bool FMixerSource::UseSourceDataOverridePlugin() const
{
return (GetNumChannels() == 1 || GetNumChannels() == 2) &&
AudioDevice->IsSourceDataOverridePluginEnabled() &&
WaveInstance->SourceDataOverridePluginSettings != nullptr;
}
}