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

3056 lines
95 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AudioMixerDevice.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "Async/Async.h"
#include "AudioAnalytics.h"
#include "AudioBusSubsystem.h"
#include "AudioDeviceNotificationSubsystem.h"
#include "AudioMixerSource.h"
#include "AudioMixerSourceManager.h"
#include "AudioMixerSourceDecode.h"
#include "AudioMixerSubmix.h"
#include "AudioMixerSourceVoice.h"
#include "AudioPluginUtilities.h"
#include "AudioMixerEffectsManager.h"
#include "DSP/Noise.h"
#include "DSP/SinOsc.h"
#include "Sound/AudioSettings.h"
#include "Sound/SoundSubmix.h"
#include "Sound/SoundSubmixSend.h"
#include "SubmixEffects/AudioMixerSubmixEffectEQ.h"
#include "SubmixEffects/AudioMixerSubmixEffectDynamicsProcessor.h"
#include "UObject/StrongObjectPtr.h"
#include "UObject/UObjectHash.h"
#include "UObject/UObjectIterator.h"
#include "IHeadMountedDisplayModule.h"
#include "ISubmixBufferListener.h"
#include "Misc/App.h"
#include "ProfilingDebugging/CsvProfiler.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "Async/Async.h"
#include "AudioDeviceNotificationSubsystem.h"
#include "Sound/AudioFormatSettings.h"
#include "HAL/PlatformMisc.h"
#if WITH_EDITOR
#include "AudioEditorModule.h"
#include "AudioEditorSettings.h"
#endif // WITH_EDITOR
#ifndef CASE_ENUM_TO_TEXT
#define CASE_ENUM_TO_TEXT(X) case X: return TEXT(#X);
#endif
const TCHAR* LexToString(const ERequiredSubmixes InType)
{
switch (InType)
{
FOREACH_ENUM_EREQUIREDSUBMIXES(CASE_ENUM_TO_TEXT)
}
return TEXT("Unknown");
}
static int32 DisableSubmixEffectEQCvar = 1;
FAutoConsoleVariableRef CVarDisableSubmixEQ(
TEXT("au.DisableSubmixEffectEQ"),
DisableSubmixEffectEQCvar,
TEXT("Disables the eq submix (true by default as of 5.0).\n")
TEXT("0: Not Disabled, 1: Disabled"),
ECVF_Default);
static int32 DisableSubmixMutationLockCVar = 0;
FAutoConsoleVariableRef CVarDisableSubmixMutationLock(
TEXT("au.DisableSubmixMutationLock"),
DisableSubmixMutationLockCVar,
TEXT("Disables the submix mutation lock.\n")
TEXT("0: Not Disabled (Default), 1: Disabled"),
ECVF_Default);
static int32 EnableAudibleDefaultEndpointSubmixesCVar = 0;
FAutoConsoleVariableRef CVarEnableAudibleDefaultEndpointSubmixes(
TEXT("au.submix.audibledefaultendpoints"),
EnableAudibleDefaultEndpointSubmixesCVar,
TEXT("Allows audio sent to defaulted (typically silent) endpoint submixes to be audible via master. (useful for debugging)\n")
TEXT("0: Disabled (Default), 1: Enabled"),
ECVF_Default);
static int32 DebugGeneratorEnableCVar = 0;
FAutoConsoleVariableRef CVarDebugGeneratorEnable(
TEXT("au.Debug.Generator"),
DebugGeneratorEnableCVar,
TEXT("Enables/disables debug sound generation.\n")
TEXT("0: Disabled, 1: SinTone, 2: WhiteNoise"),
ECVF_Default);
static float DebugGeneratorAmpCVar = 0.2f;
FAutoConsoleVariableRef CVarDebugGeneratorAmp(
TEXT("au.Debug.Generator.Amp"),
DebugGeneratorAmpCVar,
TEXT("Sets.\n")
TEXT("Default: 0.2f"),
ECVF_Default);
static int32 DebugGeneratorChannelCVar = 0;
FAutoConsoleVariableRef CVarDebugGeneratorChannel(
TEXT("au.Debug.Generator.Channel"),
DebugGeneratorChannelCVar,
TEXT("Sets channel output index of debug audio. If number provided is above supported number, uses left.\n")
TEXT("0: Left, 1: Right, etc."),
ECVF_Default);
static float DebugGeneratorFreqCVar = 440.0f;
FAutoConsoleVariableRef CVarDebugGeneratorFreq(
TEXT("au.Debug.Generator.Freq"),
DebugGeneratorFreqCVar,
TEXT("Sets debug sound generation frequency.\n")
TEXT("0: Not Disabled, 1: SinTone, 2: WhiteNoise"),
ECVF_Default);
static int32 AudioMixerPatchBufferBlocks = 3;
FAutoConsoleVariableRef CVarAudioMixerPatchBufferBlocks(
TEXT("au.PatchBufferBlocks"),
AudioMixerPatchBufferBlocks,
TEXT("Determines the number of blocks that fit in a patch buffer."),
ECVF_Default);
static int32 bMuteAudioCVar = 0;
FAutoConsoleVariableRef CVarMuteAudio(
TEXT("au.MuteAudio"),
bMuteAudioCVar,
TEXT("Mutes the audio output in the final output without effecting audio engine behavior.\n")
TEXT("1: Mutes the final submix. 0: Does not mute the final submix."),
ECVF_Cheat);
// Link to "Audio" profiling category
CSV_DECLARE_CATEGORY_MODULE_EXTERN(AUDIOMIXERCORE_API, Audio);
namespace Audio
{
void FSubmixMap::Add(const FSubmixMap::FObjectId InObjectId, FMixerSubmixPtr InMixerSubmix)
{
if (DisableSubmixMutationLockCVar)
{
SubmixMap.Add(InObjectId, InMixerSubmix);
}
else
{
FScopeLock ScopeLock(&MutationLock);
SubmixMap.Add(InObjectId, InMixerSubmix);
}
}
void FSubmixMap::Iterate(FIterFunc InFunction)
{
if (DisableSubmixMutationLockCVar)
{
for (const FPair& Pair : SubmixMap)
{
InFunction(Pair);
}
}
else
{
FScopeLock ScopeLock(&MutationLock);
for (const FPair& Pair : SubmixMap)
{
InFunction(Pair);
}
}
}
FMixerSubmixPtr FSubmixMap::FindRef(const FSubmixMap::FObjectId InObjectId) const
{
if (DisableSubmixMutationLockCVar)
{
return SubmixMap.FindRef(InObjectId);
}
else
{
FScopeLock ScopeLock(&MutationLock);
return SubmixMap.FindRef(InObjectId);
}
}
int32 FSubmixMap::Remove(const FSubmixMap::FObjectId InObjectId)
{
if (DisableSubmixMutationLockCVar)
{
return SubmixMap.Remove(InObjectId);
}
else
{
FScopeLock ScopeLock(&MutationLock);
return SubmixMap.Remove(InObjectId);
}
}
void FSubmixMap::Reset()
{
if (DisableSubmixMutationLockCVar)
{
SubmixMap.Reset();
}
else
{
FScopeLock ScopeLock(&MutationLock);
SubmixMap.Reset();
}
}
TSet<FSubmixMap::FObjectId> FSubmixMap::GetKeys() const
{
FScopeLock ScopeLock(&MutationLock);
TArray<FObjectId> Keys;
SubmixMap.GenerateKeyArray(Keys);
return TSet<FObjectId>(Keys);
}
static FAutoConsoleCommand DumpSubmixCmd(
TEXT("au.submix.drawgraph"),
TEXT("Draws the submix heirarchy for this world to the debug output"),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda([](const TArray<FString>& InArgs, UWorld* InWorld, FOutputDevice& OutLog)
{
if (InWorld)
{
if (const FMixerDevice* MixerDevice = static_cast<FMixerDevice*>(InWorld->GetAudioDeviceRaw()))
{
MixerDevice->DrawSubmixes(OutLog, InArgs);
}
}
})
);
static void DrawSubmixHeirarchy(USoundSubmixBase* InSubmix, const TSharedPtr<Audio::FMixerSubmix, ESPMode::ThreadSafe> InInstance, const FMixerDevice* InDevice, int32 InIdent, FOutputDevice& Ar, const TCHAR* GroupingText)
{
if (!InSubmix)
{
return;
}
FString Indet = FCString::Spc(InIdent*3);
FString FxChain;
if (USoundSubmix* Submix = Cast<USoundSubmix>(InSubmix))
{
for (const TObjectPtr<USoundEffectSubmixPreset>& i: Submix->SubmixEffectChain)
{
FxChain += FString::Printf(TEXT("[%s]"), *GetNameSafe(i));
}
}
Ar.Logf(TEXT("%sName=%s,Instance=0x%p,Id=%u,Fx=%s,[%s]"), *Indet, *InSubmix->GetName(), InInstance.Get(), InInstance ? InInstance->GetId() : 0, *FxChain, GroupingText);
for (const auto& i : InSubmix->ChildSubmixes)
{
DrawSubmixHeirarchy(i, InDevice->GetSubmixInstance(i).Pin(), InDevice, InIdent+1, Ar, TEXT("Static"));
}
const auto& DynamicSubmixes = InSubmix->DynamicChildSubmixes.FindOrAdd(InDevice->DeviceID);
for (const auto& i : DynamicSubmixes.ChildSubmixes)
{
DrawSubmixHeirarchy(i, InDevice->GetSubmixInstance(i).Pin(), InDevice, InIdent+1, Ar, TEXT("Dynamic"));
}
}
static void DrawSubmixInstances(const FMixerSubmix* InRoot, const int32 InIdent, FOutputDevice& InOutput)
{
if (!InRoot)
{
return;
}
const FString Indent = FCString::Spc(InIdent*3);
InOutput.Logf(TEXT("%sName=%s,Instance=0x%p,Id=%u"),
*Indent, *InRoot->GetName(), InRoot, InRoot->GetId());
// Go downwards.
const TMap<uint32, FChildSubmixInfo>& Children = InRoot->GetChildren();
for (const auto& i : Children)
{
if (FMixerSubmixPtr Child = i.Value.SubmixPtr.Pin())
{
DrawSubmixInstances(Child.Get(), InIdent+1, InOutput);
}
}
}
void FMixerDevice::DrawSubmixes(FOutputDevice& InOutput, const TArray<FString>& InArgs) const
{
InOutput.Logf(TEXT("AudioDevice=%d, Device Instance=0x%p"), DeviceID, this);
// Params.
if (Algo::FindByPredicate(InArgs, [](const FString& InStr){ return FParse::Param(*InStr, TEXT("Instances")); }) != nullptr)
{
InOutput.Logf(TEXT("[Instance Hierarchy]"));
for (int32 i = 0; i < RequiredSubmixes.Num(); i++)
{
InOutput.Logf(TEXT("SlotName=[%s]"), ToCStr(LexToString(static_cast<ERequiredSubmixes>(i))));
DrawSubmixInstances(RequiredSubmixInstances[i].Get(), 1, InOutput);
}
}
if (Algo::FindByPredicate(InArgs, [](const FString& InStr){ return FParse::Param(*InStr, TEXT("Map")); }) != nullptr)
{
InOutput.Logf(TEXT("[Map of UObject -> SubmixPtrs]"));
// Map loop of unique ids from UObjects.
TMap<uint32, USoundSubmixBase*> AllSubmixes;
for (TObjectIterator<USoundSubmixBase> It; It; ++It)
{
AllSubmixes.Add(It->GetUniqueID(),*It);
}
for (const auto i : Submixes.GetKeys())
{
const auto pFound = AllSubmixes.Find(i);
InOutput.Logf(TEXT("%u -> %s"), i, pFound ? *GetNameSafe(*pFound) : TEXT("Not found"));
}
}
// USubmixMap hierarchy from slots downwards.
InOutput.Logf(TEXT("[Map of UObject Hierarchy]"));
for (int32 i = 0; i < RequiredSubmixes.Num(); i++)
{
InOutput.Logf(TEXT("SlotName=[%s]"), ToCStr(LexToString(static_cast<ERequiredSubmixes>(i))));
DrawSubmixHeirarchy(RequiredSubmixes[i], RequiredSubmixInstances[i], this, 1, InOutput, TEXT("In Slot"));
}
}
FMixerDevice::FMixerDevice(IAudioMixerPlatformInterface* InAudioMixerPlatform)
: QuantizedEventClockManager(this)
, AudioMixerPlatform(InAudioMixerPlatform)
, AudioClockDelta(0.0)
, PreviousPrimaryVolume((float)INDEX_NONE)
, GameOrAudioThreadId(INDEX_NONE)
, AudioPlatformThreadId(INDEX_NONE)
, bDebugOutputEnabled(false)
, bSubmixRegistrationDisabled(true)
{
// This audio device is the audio mixer
bAudioMixerModuleLoaded = true;
SourceManager = MakeUnique<FMixerSourceManager>(this);
// Register AudioLink Factory.
TArray<FName> Factories = IAudioLinkFactory::GetAllRegisteredFactoryNames();
if(Factories.Num() > 0)
{
// Allow only a single registered factory instance for now.
check(Factories.Num()==1);
AudioLinkFactory=IAudioLinkFactory::FindFactory(Factories[0]);
}
}
FMixerDevice::~FMixerDevice()
{
AUDIO_MIXER_CHECK_GAME_THREAD(this);
// Shutdown all pending clock events, as they may have references to
// the FMixerSourceManager that is about to be destroyed
QuantizedEventClockManager.Shutdown();
if (AudioMixerPlatform != nullptr)
{
delete AudioMixerPlatform;
}
}
void FMixerDevice::AddReferencedObjects(FReferenceCollector& Collector)
{
}
void FMixerDevice::CheckAudioThread() const
{
#if AUDIO_MIXER_ENABLE_DEBUG_MODE
// "Audio Thread" is the game/audio thread ID used above audio rendering thread.
AUDIO_MIXER_CHECK(IsInAudioThread());
#endif
}
void FMixerDevice::OnListenerUpdated(const TArray<FListener>& InListeners)
{
LLM_SCOPE(ELLMTag::AudioMixer);
ListenerTransforms.Reset(InListeners.Num());
for (const FListener& Listener : InListeners)
{
ListenerTransforms.Add(Listener.Transform);
}
SourceManager->SetListenerTransforms(ListenerTransforms);
}
void FMixerDevice::ResetAudioRenderingThreadId()
{
AudioPlatformThreadId = INDEX_NONE;
CheckAudioRenderingThread();
}
void FMixerDevice::CheckAudioRenderingThread() const
{
if (AudioPlatformThreadId == INDEX_NONE)
{
AudioPlatformThreadId = FPlatformTLS::GetCurrentThreadId();
}
int32 CurrentThreadId = FPlatformTLS::GetCurrentThreadId();
AUDIO_MIXER_CHECK(CurrentThreadId == AudioPlatformThreadId);
}
bool FMixerDevice::IsAudioRenderingThread() const
{
int32 CurrentThreadId = FPlatformTLS::GetCurrentThreadId();
return CurrentThreadId == AudioPlatformThreadId;
}
bool FMixerDevice::IsNonRealtime() const
{
return AudioMixerPlatform && AudioMixerPlatform->IsNonRealtime();
}
TArray<Audio::FChannelPositionInfo>* FMixerDevice::GetDefaultPositionMap(int32 NumChannels)
{
const Audio::FChannelPositionInfo* SpeakerPositions = GetDefaultChannelPositions();
if (!SpeakerPositions) // speaker maps are not yet initialized
{
return nullptr;
}
switch (NumChannels)
{
// Mono speaker directly in front of listener:
case 1:
{
// force angle on single channel if we are mono
static TArray<Audio::FChannelPositionInfo> MonoMap = { {EAudioMixerChannel::FrontCenter, 0, 0} };
return &MonoMap;
}
// Stereo speakers to front left and right of listener:
case 2:
{
static TArray<Audio::FChannelPositionInfo> StereoMap = { SpeakerPositions[EAudioMixerChannel::FrontLeft], SpeakerPositions[EAudioMixerChannel::FrontRight] };
return &StereoMap;
}
// Quadrophonic speakers at each corner.
case 4:
{
static TArray<Audio::FChannelPositionInfo> QuadMap = {
SpeakerPositions[EAudioMixerChannel::FrontLeft] //left
,SpeakerPositions[EAudioMixerChannel::FrontRight] // right
,SpeakerPositions[EAudioMixerChannel::SideLeft] //Left Surround
,SpeakerPositions[EAudioMixerChannel::SideRight] //Right Surround
};
return &QuadMap;
}
// 5.1 speakers.
case 6:
{
static TArray<Audio::FChannelPositionInfo> FiveDotOneMap = {
SpeakerPositions[EAudioMixerChannel::FrontLeft] //left
,SpeakerPositions[EAudioMixerChannel::FrontRight] // right
,SpeakerPositions[EAudioMixerChannel::FrontCenter] //center
,SpeakerPositions[EAudioMixerChannel::LowFrequency] //LFE
,SpeakerPositions[EAudioMixerChannel::SideLeft] //Left Rear
,SpeakerPositions[EAudioMixerChannel::SideRight] //Right Rear
};
return &FiveDotOneMap;
}
// 7.1 speakers.
case 8:
{
static TArray<Audio::FChannelPositionInfo> SevenDotOneMap = {
SpeakerPositions[EAudioMixerChannel::FrontLeft] // left
,SpeakerPositions[EAudioMixerChannel::FrontRight] // right
,SpeakerPositions[EAudioMixerChannel::FrontCenter] //center
,SpeakerPositions[EAudioMixerChannel::LowFrequency] //LFE
,SpeakerPositions[EAudioMixerChannel::BackLeft] // Left Rear
,SpeakerPositions[EAudioMixerChannel::BackRight] // Right Rear
,SpeakerPositions[EAudioMixerChannel::SideLeft] // Left Surround
,SpeakerPositions[EAudioMixerChannel::SideRight] // Right Surround
};
return &SevenDotOneMap;
}
case 0:
default:
{
return nullptr;
}
}
}
bool FMixerDevice::IsEndpointSubmix(const USoundSubmixBase* InSubmix)
{
return InSubmix && (InSubmix->IsA<UEndpointSubmix>() || InSubmix->IsA<USoundfieldEndpointSubmix>());
}
void FMixerDevice::UpdateDeviceDeltaTime()
{
DeviceDeltaTime = GetGameDeltaTime();
}
void FMixerDevice::GetAudioDeviceList(TArray<FString>& OutAudioDeviceNames) const
{
if (AudioMixerPlatform && AudioMixerPlatform->IsInitialized())
{
uint32 NumOutputDevices;
if (AudioMixerPlatform->GetNumOutputDevices(NumOutputDevices))
{
for (uint32 i = 0; i < NumOutputDevices; ++i)
{
FAudioPlatformDeviceInfo DeviceInfo;
if (AudioMixerPlatform->GetOutputDeviceInfo(i, DeviceInfo))
{
OutAudioDeviceNames.Add(DeviceInfo.Name);
}
}
}
}
}
bool FMixerDevice::InitializeHardware()
{
ensure(IsInGameThread());
LLM_SCOPE(ELLMTag::AudioMixer);
if (AudioMixerPlatform && AudioMixerPlatform->InitializeHardware())
{
UE_LOG(LogAudioMixer, Display, TEXT("Initializing audio mixer using platform API: '%s'"), *AudioMixerPlatform->GetPlatformApi());
// Issue a log if the audio engine will be muted.
#if !UE_BUILD_SHIPPING
if (bMuteAudioCVar || FApp::IsAudioMuted())
{
UE_LOG(LogAudioMixer, Display, TEXT("Audio output will be muted. If this not intentional, check for au.MuteAudio cvar or -muteaudio command line."));
}
#endif // !UE_BUILD_SHIPPING
const UAudioSettings* AudioSettings = GetDefault<UAudioSettings>();
MonoChannelUpmixMethod = AudioSettings->MonoChannelUpmixMethod;
PanningMethod = AudioSettings->PanningMethod;
// Set whether we're the main audio mixer
bIsMainAudioMixer = IsMainAudioDevice();
AUDIO_MIXER_CHECK(SampleRate != 0.0f);
AudioMixerPlatform->RegisterDeviceChangedListener();
// Allow platforms to override the platform settings callback buffer frame size (i.e. restrict to particular values, etc)
PlatformSettings.CallbackBufferFrameSize = AudioMixerPlatform->GetNumFrames(PlatformSettings.CallbackBufferFrameSize);
OpenStreamParams.NumBuffers = PlatformSettings.NumBuffers;
OpenStreamParams.NumFrames = PlatformSettings.CallbackBufferFrameSize;
OpenStreamParams.OutputDeviceIndex = AUDIO_MIXER_DEFAULT_DEVICE_INDEX; // TODO: Support overriding which audio device user wants to open, not necessarily default.
OpenStreamParams.SampleRate = SampleRate;
OpenStreamParams.AudioMixer = this;
OpenStreamParams.MaxSources = GetMaxSources();
#if WITH_EDITOR
{
IAudioEditorModule& AudioEditorModule = FModuleManager::LoadModuleChecked<IAudioEditorModule>("AudioEditor");
TOptional<FAudioEditorDeviceSettings> DeviceSettings = AudioEditorModule.GetAudioEditorDeviceSettings();
if (DeviceSettings.IsSet())
{
OpenStreamParams.bUseSystemAudioDevice = DeviceSettings->bUseSystemDevice;
OpenStreamParams.AudioDeviceId = DeviceSettings->DeviceId;
}
}
#endif //WITH_EDITOR
FString DefaultDeviceName = AudioMixerPlatform->GetDefaultDeviceName();
// Allow HMD to specify audio device, if one was not specified in settings
if (DefaultDeviceName.IsEmpty() && FAudioDevice::CanUseVRAudioDevice() && IHeadMountedDisplayModule::IsAvailable())
{
DefaultDeviceName = IHeadMountedDisplayModule::Get().GetAudioOutputDevice();
}
FString DeviceToOpen = DefaultDeviceName;
if (!OpenStreamParams.bUseSystemAudioDevice)
{
DeviceToOpen = OpenStreamParams.AudioDeviceId;
}
if (!DeviceToOpen.IsEmpty())
{
uint32 NumOutputDevices = 0;
AudioMixerPlatform->GetNumOutputDevices(NumOutputDevices);
for (uint32 i = 0; i < NumOutputDevices; ++i)
{
FAudioPlatformDeviceInfo DeviceInfo;
AudioMixerPlatform->GetOutputDeviceInfo(i, DeviceInfo);
if (DeviceInfo.Name == DeviceToOpen || DeviceInfo.DeviceId == DeviceToOpen)
{
OpenStreamParams.OutputDeviceIndex = i;
// If we're intentionally selecting an audio device (and not just using the default device) then
// lets try to restore audio to that device if it's removed and then later is restored
OpenStreamParams.bRestoreIfRemoved = true;
break;
}
}
}
if (AudioMixerPlatform->OpenAudioStream(OpenStreamParams))
{
// Get the platform device info we're using
PlatformInfo = AudioMixerPlatform->GetPlatformDeviceInfo();
UE_LOG(LogAudioMixer, Display, TEXT("Using Audio Hardware Device %s"), *PlatformInfo.Name);
// Initialize some data that depends on speaker configuration, etc.
InitializeChannelAzimuthMap(PlatformInfo.NumChannels);
FSourceManagerInitParams SourceManagerInitParams;
SourceManagerInitParams.NumSources = GetMaxSources();
AudioClock = 0.0;
AudioClockDelta = (double)OpenStreamParams.NumFrames / OpenStreamParams.SampleRate;
AudioClockTimingData.UpdateTime = 0.0;
PluginInitializationParams.NumSources = SourceManagerInitParams.NumSources;
PluginInitializationParams.SampleRate = SampleRate;
PluginInitializationParams.BufferLength = OpenStreamParams.NumFrames;
PluginInitializationParams.AudioDevicePtr = this;
{
LLM_SCOPE(ELLMTag::AudioMixerPlugins);
// Initialize any plugins if they exist
// spatialization
SetCurrentSpatializationPlugin(AudioPluginUtilities::GetDesiredSpatializationPluginName());
if (OcclusionInterface.IsValid())
{
OcclusionInterface->Initialize(PluginInitializationParams);
}
if (ReverbPluginInterface.IsValid())
{
ReverbPluginInterface->Initialize(PluginInitializationParams);
}
if (SourceDataOverridePluginInterface.IsValid())
{
SourceDataOverridePluginInterface->Initialize(PluginInitializationParams);
}
}
// initialize the source manager after our plugins are spun up (cached by sources)
SourceManager->Init(SourceManagerInitParams);
// Need to set these up before we start the audio stream.
InitSoundSubmixes();
AudioMixerPlatform->PostInitializeHardware();
// Initialize the data used for audio thread sub-frame timing.
AudioThreadTimingData.StartTime = FPlatformTime::Seconds();
AudioThreadTimingData.AudioThreadTime = 0.0;
AudioThreadTimingData.AudioRenderThreadTime = 0.0;
// Create synchronized Audio Task Queue for this device...
CreateSynchronizedAudioTaskQueue((Audio::AudioTaskQueueId)DeviceID);
Audio::Analytics::RecordEvent_Usage(TEXT("ProjectSettings"), MakeAnalyticsEventAttributeArray(
TEXT("SampleRate"), PlatformSettings.SampleRate,
TEXT("BufferSize"), PlatformSettings.CallbackBufferFrameSize,
TEXT("NumBuffers"), PlatformSettings.NumBuffers,
TEXT("NumSources"), PlatformSettings.MaxChannels,
TEXT("NumOutputChannels"), PlatformInfo.NumChannels));
// Start streaming audio
return AudioMixerPlatform->StartAudioStream();
}
}
else if (AudioMixerPlatform)
{
UE_LOG(LogAudioMixer, Warning, TEXT("Failed to initialize audio mixer for platform API: '%s'"), *AudioMixerPlatform->GetPlatformApi());
}
return false;
}
void FMixerDevice::FadeIn()
{
AudioMixerPlatform->FadeIn();
}
void FMixerDevice::FadeOut()
{
// In editor builds, we aren't going to fade out the main audio device.
#if WITH_EDITOR
if (!IsMainAudioDevice())
#endif
{
AudioMixerPlatform->FadeOut();
}
}
void FMixerDevice::TeardownHardware()
{
ensure(IsInGameThread());
if (IsInitialized())
{
for (TObjectIterator<USoundSubmix> It; It; ++It)
{
UnregisterSoundSubmix(*It, true);
}
// Destroy the synchronized Audio Task Queue for this device
DestroySynchronizedAudioTaskQueue((Audio::AudioTaskQueueId)DeviceID);
}
// reset all the sound effect presets loaded
#if WITH_EDITOR
for (TObjectIterator<USoundEffectPreset> It; It; ++It)
{
USoundEffectPreset* SoundEffectPreset = *It;
SoundEffectPreset->Init();
}
#endif
if (AudioMixerPlatform)
{
SourceManager->Update();
AudioMixerPlatform->UnregisterDeviceChangedListener();
AudioMixerPlatform->StopAudioStream();
AudioMixerPlatform->CloseAudioStream();
AudioMixerPlatform->TeardownHardware();
}
// Reset existing submixes if they exist
RequiredSubmixInstances.Reset();
Submixes.Reset();
}
void FMixerDevice::UpdateHardwareTiming()
{
// Get the relative audio thread time (from start of audio engine)
// Add some jitter delta to account for any audio thread timing jitter.
const double AudioThreadJitterDelta = AudioClockDelta;
AudioThreadTimingData.AudioThreadTime = FPlatformTime::Seconds() - AudioThreadTimingData.StartTime + AudioThreadJitterDelta;
}
void FMixerDevice::UpdateGameThread()
{
LLM_SCOPE(ELLMTag::AudioMixer);
// Pump our command queue sending commands to the game thread
PumpGameThreadCommandQueue();
}
void FMixerDevice::UpdateHardware()
{
LLM_SCOPE(ELLMTag::AudioMixer);
// If we're in editor, re-query these in case they changed.
if (GIsEditor)
{
const UAudioSettings* AudioSettings = GetDefault<UAudioSettings>();
MonoChannelUpmixMethod = AudioSettings->MonoChannelUpmixMethod;
PanningMethod = AudioSettings->PanningMethod;
}
SourceManager->Update();
AudioMixerPlatform->OnHardwareUpdate();
if (AudioMixerPlatform->CheckAudioDeviceChange())
{
// Get the platform device info we're using
PlatformInfo = AudioMixerPlatform->GetPlatformDeviceInfo();
// Initialize some data that depends on speaker configuration, etc.
InitializeChannelAzimuthMap(PlatformInfo.NumChannels);
// Update the channel device count in case it changed
SourceManager->UpdateDeviceChannelCount(PlatformInfo.NumChannels);
// Reset rendering thread ID to this thread ID so that commands can
// be flushed. Audio Rendering Thread ID will be reset again in call
// to FMixerDevice::OnProcessAudio
ResetAudioRenderingThreadId();
// Cache the audio platform thread id for debugging purposes. This is
// an attempt to narrow down the causes of UE-209237. The theory is
// that the there are multiple threads attempting to run audio rendering
// commands. To catch the issue we attempt to cache the thread id before
// running audio rendering commands. We then check that the thread id
// has not changed for the duration that render thread commands have
// run.
std::atomic<int32> CurrentAudioPlatformThreadId = AudioPlatformThreadId.load();
// Force source manager to incorporate device channel count change.
FlushAudioRenderingCommands(true /* bPumpSynchronously */);
if (UAudioDeviceNotificationSubsystem* AudioDeviceNotifSubsystem = UAudioDeviceNotificationSubsystem::Get())
{
AudioDeviceNotifSubsystem->OnDeviceSwitched(PlatformInfo.DeviceId);
}
// Related to earlier mention of UE-209237
UE_CLOG(CurrentAudioPlatformThreadId != AudioPlatformThreadId, LogAudioMixer, Error, TEXT("Platform audio thread id changed while flushing render commands. Expected %d, found %d. May result in corrupt internal audio source state."), CurrentAudioPlatformThreadId.load(), AudioPlatformThreadId.load());
// Audio rendering was suspended in CheckAudioDeviceChange if it changed.
AudioMixerPlatform->ResumePlaybackOnNewDevice();
}
// Device must be initialized prior to call as submix graph may not be ready yet otherwise.
if (IsInitialized())
{
// Loop through any envelope-following submixes and perform any broadcasting of envelope data if needed
TArray<float> SubmixEnvelopeData;
for (USoundSubmix* SoundSubmix : DelegateBoundSubmixes)
{
if (SoundSubmix)
{
// Retrieve the submix instance and the envelope data and broadcast on the audio thread.
Audio::FMixerSubmixWeakPtr SubmixPtr = GetSubmixInstance(SoundSubmix);
if (SubmixPtr.IsValid())
{
FAudioThread::RunCommandOnGameThread([this, SubmixPtr]()
{
Audio::FMixerSubmixPtr ThisSubmixPtr = SubmixPtr.Pin();
if (ThisSubmixPtr.IsValid())
{
ThisSubmixPtr->BroadcastDelegates();
}
});
}
}
}
// Check if the background mute changed state and update the submixes which are enabled to do background muting.
const float CurrentPrimaryVolume = GetPrimaryVolume();
if (!FMath::IsNearlyEqual(PreviousPrimaryVolume, CurrentPrimaryVolume))
{
PreviousPrimaryVolume = CurrentPrimaryVolume;
bool IsMuted = FMath::IsNearlyZero(CurrentPrimaryVolume);
for (TObjectIterator<USoundSubmix> It; It; ++It)
{
if (It->bMuteWhenBackgrounded)
{
FMixerSubmixPtr SubmixInstance = GetSubmixInstance(*It).Pin();
if (SubmixInstance.IsValid())
{
SubmixInstance->SetBackgroundMuted(IsMuted);
}
}
}
}
}
}
double FMixerDevice::GetAudioTime() const
{
return AudioClock;
}
double FMixerDevice::GetInterpolatedAudioClock() const
{
return AudioClockTimingData.GetInterpolatedAudioClock(AudioClock, AudioClockDelta);
}
FAudioEffectsManager* FMixerDevice::CreateEffectsManager()
{
return new FAudioMixerEffectsManager(this);
}
FSoundSource* FMixerDevice::CreateSoundSource()
{
return new FMixerSource(this);
}
bool FMixerDevice::HasCompressedAudioInfoClass(USoundWave* InSoundWave)
{
check(InSoundWave);
check(AudioMixerPlatform);
// Every platform has compressed audio.
return true;
}
bool FMixerDevice::SupportsRealtimeDecompression() const
{
// Every platform supports realtime decompression.
return true;
}
bool FMixerDevice::DisablePCMAudioCaching() const
{
return AudioMixerPlatform->DisablePCMAudioCaching();
}
bool FMixerDevice::ValidateAPICall(const TCHAR* Function, uint32 ErrorCode)
{
return false;
}
#if UE_ALLOW_EXEC_COMMANDS
bool FMixerDevice::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
{
if (FAudioDevice::Exec(InWorld, Cmd, Ar))
{
return true;
}
return false;
}
#endif // UE_ALLOW_EXEC_COMMANDS
void FMixerDevice::CountBytes(FArchive& InArchive)
{
FAudioDevice::CountBytes(InArchive);
}
bool FMixerDevice::IsExernalBackgroundSoundActive()
{
return false;
}
void FMixerDevice::ResumeContext()
{
AudioMixerPlatform->ResumeContext();
}
void FMixerDevice::SuspendContext()
{
AudioMixerPlatform->SuspendContext();
}
void FMixerDevice::EnableDebugAudioOutput()
{
bDebugOutputEnabled = true;
}
bool FMixerDevice::OnProcessAudioStream(FAlignedFloatBuffer& Output)
{
LLM_SCOPE(ELLMTag::AudioMixer);
// This function could be called in a task manager, which means the thread ID may change between calls.
ResetAudioRenderingThreadId();
// Cache the audio platform thread id for debugging purposes. This is
// an attempt to narrow down the causes of UE-209237. The theory is
// that the there are multiple threads attempting to run audio rendering
// commands. To catch the issue we attempt to cache the thread id before
// running audio rendering commands. We then check that the thread id
// has not changed for the duration that render thread commands have
// run.
std::atomic<int32> CurrentAudioPlatformThreadId = AudioPlatformThreadId.load();
// Update the audio render thread time at the head of the render
AudioThreadTimingData.AudioRenderThreadTime = FPlatformTime::Seconds() - AudioThreadTimingData.StartTime;
// notify interested parties
FAudioDeviceRenderInfo RenderInfo;
RenderInfo.NumFrames = SourceManager->GetNumOutputFrames();
NotifyAudioDevicePreRender(RenderInfo);
// Pump the command queue to the audio render thread
PumpCommandQueue();
// update the clock manager
QuantizedEventClockManager.Update(SourceManager->GetNumOutputFrames());
// Compute the next block of audio in the source manager
SourceManager->ComputeNextBlockOfSamples();
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
{
CSV_SCOPED_TIMING_STAT(Audio, Submixes);
SCOPE_CYCLE_COUNTER(STAT_AudioMixerSubmixes);
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
if (MainSubmixPtr.IsValid())
{
// Process the audio output from the master submix
MainSubmixPtr->ProcessAudio(Output);
#if !UE_BUILD_SHIPPING
// This is done at the very end here to avoid changing the behavior in any other way (analysis, profiling, etc)
if ((bMuteAudioCVar || FApp::IsAudioMuted()))
{
FMemory::Memzero((void*)Output.GetData(), sizeof(float) * Output.Num());
}
#endif // !UE_BUILD_SHIPPING
}
}
// Only after submixes have finished rendering audio, update the source state in the source manager to reflect sound-doneness, etc
SourceManager->UpdateSourceState();
{
CSV_SCOPED_TIMING_STAT(Audio, EndpointSubmixes);
SCOPE_CYCLE_COUNTER(STAT_AudioMixerEndpointSubmixes);
FScopeLock ScopeLock(&EndpointSubmixesMutationLock);
if (EnableAudibleDefaultEndpointSubmixesCVar !=0 )
{
for (const FMixerSubmixPtr& Submix : DefaultEndpointSubmixes)
{
// If this hit, a submix was added to the default submix endpoint array
// even though it's not an endpoint, or a parent was set on an endpoint submix
// and it wasn't removed from DefaultEndpointSubmixes.
ensure(Submix->IsDefaultEndpointSubmix());
// Any endpoint submixes that don't specify an endpoint
// are summed into our master output.
Submix->ProcessAudio(Output);
}
}
for (FMixerSubmixPtr& Submix : ExternalEndpointSubmixes)
{
// If this hit, a submix was added to the external submix endpoint array
// even though it's not an endpoint, or a parent was set on an endpoint submix
// and it wasn't removed from ExternalEndpointSubmixes.
ensure(Submix->IsExternalEndpointSubmix());
Submix->ProcessAudioAndSendToEndpoint();
}
}
// Reset stopping sounds and clear their state after submixes have been mixed
SourceManager->ClearStoppingSounds();
// Do any debug output performing
if (bDebugOutputEnabled || DebugGeneratorEnableCVar > 0)
{
if (DebugGeneratorEnableCVar < 2)
{
SineOscTest(Output);
}
else
{
WhiteNoiseTest(Output);
}
}
// Update the audio clock
UpdateAudioClock();
// notify interested parties
NotifyAudioDevicePostRender(RenderInfo);
KickQueuedTasks((Audio::AudioTaskQueueId)DeviceID);
// Related to earlier mention of UE-209237
UE_CLOG(CurrentAudioPlatformThreadId != AudioPlatformThreadId, LogAudioMixer, Error, TEXT("Platform audio thread id changed while flushing render commands. Expected %d, found %d. May result in corrupt internal audio source state."), CurrentAudioPlatformThreadId.load(), AudioPlatformThreadId.load());
return true;
}
void FMixerDevice::UpdateAudioClock()
{
AudioClock += AudioClockDelta;
AudioClockTimingData.UpdateTime = FPlatformTime::Seconds();
}
void FMixerDevice::OnAudioStreamShutdown()
{
// Make sure the source manager pumps any final commands on shutdown. These allow for cleaning up sources, interfacing with plugins, etc.
// Because we double buffer our command queues, we call this function twice to ensure all commands are successfully pumped.
SourceManager->PumpCommandQueue();
SourceManager->PumpCommandQueue();
// Make sure we force any pending release data to happen on shutdown
SourceManager->UpdatePendingReleaseData(true);
}
void FMixerDevice::LoadRequiredSubmix(ERequiredSubmixes InType, const FString& InDefaultName, bool bInDefaultMuteWhenBackgrounded, FSoftObjectPath& InObjectPath)
{
check(IsInGameThread());
const int32 RequiredSubmixCount = static_cast<int32>(ERequiredSubmixes::Count);
if(RequiredSubmixes.Num() < RequiredSubmixCount)
{
RequiredSubmixes.AddZeroed(RequiredSubmixCount - RequiredSubmixes.Num());
}
if (RequiredSubmixInstances.Num() < RequiredSubmixCount)
{
RequiredSubmixInstances.AddZeroed(RequiredSubmixCount - RequiredSubmixInstances.Num());
}
const int32 TypeIndex = static_cast<int32>(InType);
if (USoundSubmix* OldSubmix = RequiredSubmixes[TypeIndex])
{
// Don't bother swapping if new path is invalid...
if (!InObjectPath.IsValid())
{
return;
}
// or is same object already initialized.
if (InObjectPath.GetAssetPathString() == OldSubmix->GetPathName())
{
return;
}
OldSubmix->RemoveFromRoot();
FMixerSubmixPtr OldSubmixPtr = RequiredSubmixInstances[TypeIndex];
if (OldSubmixPtr.IsValid())
{
FMixerSubmixPtr ParentSubmixPtr = RequiredSubmixInstances[TypeIndex]->GetParentSubmix().Pin();
if (ParentSubmixPtr.IsValid())
{
ParentSubmixPtr->RemoveChildSubmix(RequiredSubmixInstances[TypeIndex]);
}
}
}
// 1. Try loading from Developer Audio Settings
USoundSubmix* NewSubmix = Cast<USoundSubmix>(InObjectPath.TryLoad());
// 2. If Unset or not found, fallback to engine asset
if (!NewSubmix)
{
static const FString EngineSubmixDir = TEXT("/Engine/EngineSounds/Submixes");
InObjectPath = FString::Printf(TEXT("%s/%s.%s"), *EngineSubmixDir, *InDefaultName, *InDefaultName);
NewSubmix = Cast<USoundSubmix>(InObjectPath.TryLoad());
UE_LOG(LogAudioMixer, Display, TEXT("Submix unset or invalid in 'AudioSettings': Using engine asset '%s'"),
*InDefaultName,
*InObjectPath.GetAssetPathString());
}
// 3. If engine version not found, dynamically spawn and post error
if (!NewSubmix)
{
UE_LOG(LogAudioMixer, Error, TEXT("Failed to load submix from engine asset path '%s'. Creating '%s' as a stub."),
*InObjectPath.GetAssetPathString(),
*InDefaultName);
NewSubmix = NewObject<USoundSubmix>(USoundSubmix::StaticClass(), *InDefaultName);
// Make the master reverb mute when backgrounded
NewSubmix->bMuteWhenBackgrounded = bInDefaultMuteWhenBackgrounded;
}
check(NewSubmix);
NewSubmix->AddToRoot();
// If sharing submix with other explicitly defined MainSubmix, create
// shared pointer directed to already existing submix instance. Otherwise,
// create a new version.
FMixerSubmixPtr NewMixerSubmix = GetRequiredSubmixInstance(NewSubmix);
if (!NewMixerSubmix.IsValid())
{
UE_LOG(LogAudioMixer, Display, TEXT("Creating Master Submix '%s'"), *NewSubmix->GetName());
NewMixerSubmix = MakeShared<FMixerSubmix, ESPMode::ThreadSafe>(this);
}
// Ensure that master submixes are ONLY tracked in master submix array.
// RequiredSubmixes array can share instances, but should not be duplicated in Submixes Map.
if (Submixes.Remove(NewSubmix->GetUniqueID()) > 0)
{
UE_LOG(LogAudioMixer, Display, TEXT("Submix '%s' has been promoted to master array."), *NewSubmix->GetName());
}
// Update/add new submix and instance to respective master arrays
RequiredSubmixes[TypeIndex] = NewSubmix;
RequiredSubmixInstances[TypeIndex] = NewMixerSubmix;
//Note: If we support using endpoint/soundfield submixes as a master submix in the future, we will need to call NewMixerSubmix->SetSoundfieldFactory here.
NewMixerSubmix->Init(NewSubmix, false /* bAllowReInit */);
}
void FMixerDevice::LoadPluginSoundSubmixes()
{
check(IsInGameThread());
if (IsReverbPluginEnabled() && ReverbPluginInterface)
{
LLM_SCOPE(ELLMTag::AudioMixerPlugins);
USoundSubmix* ReverbPluginSubmix = ReverbPluginInterface->LoadSubmix();
check(ReverbPluginSubmix);
ReverbPluginSubmix->AddToRoot();
LoadSoundSubmix(*ReverbPluginSubmix);
InitSoundfieldAndEndpointDataForSubmix(*ReverbPluginSubmix, GetSubmixInstance(ReverbPluginSubmix).Pin(), false);
// Plugin must provide valid effect to enable reverb
FSoundEffectSubmixPtr ReverbPluginEffectSubmix = ReverbPluginInterface->GetEffectSubmix();
if (ReverbPluginEffectSubmix.IsValid())
{
if (USoundEffectPreset* Preset = ReverbPluginEffectSubmix->GetPreset())
{
FMixerSubmixPtr ReverbPluginMixerSubmixPtr = GetSubmixInstance(ReverbPluginSubmix).Pin();
check(ReverbPluginMixerSubmixPtr.IsValid());
const TWeakObjectPtr<USoundSubmix> ReverbPluginSubmixPtr = ReverbPluginSubmix;
FMixerSubmixWeakPtr ReverbPluginMixerSubmixWeakPtr = ReverbPluginMixerSubmixPtr;
AudioRenderThreadCommand([ReverbPluginMixerSubmixWeakPtr, ReverbPluginSubmixPtr, ReverbPluginEffectSubmix]()
{
FMixerSubmixPtr PluginSubmixPtr = ReverbPluginMixerSubmixWeakPtr.Pin();
if (PluginSubmixPtr.IsValid() && ReverbPluginSubmixPtr.IsValid())
{
PluginSubmixPtr->ReplaceSoundEffectSubmix(0, ReverbPluginEffectSubmix);
}
});
}
}
else
{
UE_LOG(LogAudioMixer, Error, TEXT("Reverb plugin failed to provide valid effect submix. Plugin audio processing disabled."));
}
}
}
void FMixerDevice::InitSoundSubmixes()
{
if (IsInGameThread())
{
bSubmixRegistrationDisabled = true;
UAudioSettings* AudioSettings = GetMutableDefault<UAudioSettings>();
check(AudioSettings);
if (RequiredSubmixes.Num() > 0)
{
UE_LOG(LogAudioMixer, Display, TEXT("Re-initializing Sound Submixes..."));
}
else
{
UE_LOG(LogAudioMixer, Display, TEXT("Initializing Sound Submixes..."));
}
// 1. Load or reload all sound submixes/instances
LoadRequiredSubmix(ERequiredSubmixes::Main, TEXT("MasterSubmixDefault"), false /* DefaultMuteWhenBackgrounded */, AudioSettings->MasterSubmix);
// BaseDefaultSubmix is an optional master submix type set by project settings
if (AudioSettings->BaseDefaultSubmix.IsValid())
{
LoadRequiredSubmix(ERequiredSubmixes::BaseDefault, TEXT("BaseDefault"), false /* DefaultMuteWhenBackgrounded */, AudioSettings->BaseDefaultSubmix);
}
LoadRequiredSubmix(ERequiredSubmixes::Reverb, TEXT("MasterReverbSubmixDefault"), true /* DefaultMuteWhenBackgrounded */, AudioSettings->ReverbSubmix);
if (!DisableSubmixEffectEQCvar)
{
LoadRequiredSubmix(ERequiredSubmixes::EQ, TEXT("MasterEQSubmixDefault"), false /* DefaultMuteWhenBackgrounded */, AudioSettings->EQSubmix);
}
LoadPluginSoundSubmixes();
for (TObjectIterator<USoundSubmixBase> It; It; ++It)
{
USoundSubmixBase* SubmixToLoad = *It;
check(SubmixToLoad);
if (!IsRequiredSubmixType(SubmixToLoad) && !SubmixToLoad->IsDynamic( true /* bIncludeAncestors */) ) // Do not load dynamic submixes until they've been connected.
{
LoadSoundSubmix(*SubmixToLoad);
InitSoundfieldAndEndpointDataForSubmix(*SubmixToLoad, GetSubmixInstance(SubmixToLoad).Pin(), false);
}
}
bSubmixRegistrationDisabled = false;
}
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.InitSoundSubmixes"), STAT_InitSoundSubmixes, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this]()
{
CSV_SCOPED_TIMING_STAT(Audio, InitSubmix);
InitSoundSubmixes();
}, GET_STATID(STAT_InitSoundSubmixes));
return;
}
for (int32 i = 0; i < static_cast<int32>(ERequiredSubmixes::Count); ++i)
{
if (DisableSubmixEffectEQCvar && i == static_cast<int32>(ERequiredSubmixes::EQ))
{
continue;
}
USoundSubmixBase* SoundSubmix = RequiredSubmixes[i];
if (SoundSubmix && SoundSubmix != RequiredSubmixes[static_cast<int32>(ERequiredSubmixes::Main)])
{
FMixerSubmixPtr& MainSubmixInstance = RequiredSubmixInstances[i];
RebuildSubmixLinks(*SoundSubmix, MainSubmixInstance);
}
}
for (TObjectIterator<const USoundSubmixBase> It; It; ++It)
{
if (const USoundSubmixBase* SubmixBase = *It)
{
if (IsRequiredSubmixType(SubmixBase))
{
continue;
}
FMixerSubmixPtr SubmixPtr = Submixes.FindRef(SubmixBase->GetUniqueID());
if (SubmixPtr.IsValid())
{
RebuildSubmixLinks(*SubmixBase, SubmixPtr);
}
}
}
}
void FMixerDevice::RebuildSubmixLinks(const USoundSubmixBase& SoundSubmix, FMixerSubmixPtr& SubmixInstance)
{
// Setup up the submix instance's parent and add the submix instance as a child
FMixerSubmixPtr ParentSubmixInstance;
if (const USoundSubmixWithParentBase* SubmixWithParent = Cast<const USoundSubmixWithParentBase>(&SoundSubmix))
{
if (TObjectPtr<USoundSubmixBase> Parent = SubmixWithParent->GetParent(DeviceID); Parent)
{
ParentSubmixInstance = GetSubmixInstance(Parent).Pin();
}
else if (!SubmixWithParent->IsDynamic( true /*bIncludeAncestors*/ )) // Dynamic submixes do not auto connect.
{
// If this submix is itself the broadcast submix, set its parent to the master submix
if (SubmixInstance == RequiredSubmixInstances[static_cast<int32>(ERequiredSubmixes::BaseDefault)])
{
ParentSubmixInstance = GetMasterSubmix().Pin();
}
else
{
ParentSubmixInstance = GetBaseDefaultSubmix().Pin();
}
}
}
if (ParentSubmixInstance.IsValid())
{
SubmixInstance->SetParentSubmix(ParentSubmixInstance);
ParentSubmixInstance->AddChildSubmix(SubmixInstance);
}
}
FAudioPlatformSettings FMixerDevice::GetPlatformSettings() const
{
FAudioPlatformSettings
Settings;
if (AudioMixerPlatform)
{
Settings = AudioMixerPlatform->GetPlatformSettings();
const int32 DefaultMaxChannels = GetDefault<UAudioSettings>()->GetHighestMaxChannels();
UE_LOG(LogAudioMixer, Display, TEXT("Audio Mixer Platform Settings:"));
UE_LOG(LogAudioMixer, Display, TEXT(" Sample Rate: %d"), Settings.SampleRate);
UE_LOG(LogAudioMixer, Display, TEXT(" Callback Buffer Frame Size Requested: %d"), Settings.CallbackBufferFrameSize);
UE_LOG(LogAudioMixer, Display, TEXT(" Callback Buffer Frame Size To Use: %d"), AudioMixerPlatform->GetNumFrames(Settings.CallbackBufferFrameSize));
UE_LOG(LogAudioMixer, Display, TEXT(" Number of buffers to queue: %d"), Settings.NumBuffers);
UE_LOG(LogAudioMixer, Display, TEXT(" Max Channels (voices): %d"), (Settings.MaxChannels > 0) ? Settings.MaxChannels : DefaultMaxChannels);
UE_LOG(LogAudioMixer, Display, TEXT(" Number of Async Source Workers: %d"), Settings.NumSourceWorkers);
}
return Settings;
}
FMixerSubmixWeakPtr FMixerDevice::GetMasterSubmix()
{
return GetMainSubmix();
}
FMixerSubmixWeakPtr FMixerDevice::GetMainSubmix()
{
return RequiredSubmixInstances[(int32)ERequiredSubmixes::Main];
}
FMixerSubmixWeakPtr FMixerDevice::GetBaseDefaultSubmix()
{
if (RequiredSubmixInstances[(int32)ERequiredSubmixes::BaseDefault].IsValid())
{
return RequiredSubmixInstances[(int32)ERequiredSubmixes::BaseDefault];
}
return GetMasterSubmix();
}
FMixerSubmixWeakPtr FMixerDevice::GetReverbSubmix()
{
return RequiredSubmixInstances[(int32)ERequiredSubmixes::Reverb];
}
FMixerSubmixWeakPtr FMixerDevice::GetEQSubmix()
{
return RequiredSubmixInstances[(int32)ERequiredSubmixes::EQ];
}
FMixerSubmixWeakPtr FMixerDevice::GetMasterReverbSubmix()
{
return RequiredSubmixInstances[(int32)ERequiredSubmixes::Reverb];
}
FMixerSubmixWeakPtr FMixerDevice::GetMasterEQSubmix()
{
return RequiredSubmixInstances[(int32)ERequiredSubmixes::EQ];
}
void FMixerDevice::AddMainSubmixEffect(FSoundEffectSubmixPtr SoundEffectSubmix)
{
AudioRenderThreadCommand([this, SoundEffectSubmix]()
{
RequiredSubmixInstances[(int32)ERequiredSubmixes::Main]->AddSoundEffectSubmix(SoundEffectSubmix);
});
}
void FMixerDevice::AddMasterSubmixEffect(FSoundEffectSubmixPtr SoundEffectSubmix)
{
AddMainSubmixEffect(SoundEffectSubmix);
}
void FMixerDevice::RemoveMainSubmixEffect(uint32 SubmixEffectId)
{
AudioRenderThreadCommand([this, SubmixEffectId]()
{
RequiredSubmixInstances[(int32)ERequiredSubmixes::Main]->RemoveSoundEffectSubmix(SubmixEffectId);
});
}
void FMixerDevice::RemoveMasterSubmixEffect(uint32 SubmixEffectId)
{
RemoveMainSubmixEffect(SubmixEffectId);
}
void FMixerDevice::ClearMasterSubmixEffects()
{
ClearMainSubmixEffects();
}
void FMixerDevice::ClearMainSubmixEffects()
{
AudioRenderThreadCommand([this]()
{
RequiredSubmixInstances[(int32)ERequiredSubmixes::Main]->ClearSoundEffectSubmixes();
});
}
int32 FMixerDevice::AddSubmixEffect(USoundSubmix* InSoundSubmix, FSoundEffectSubmixPtr SoundEffect)
{
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
int32 NumEffects = MixerSubmixPtr->GetNumEffects();
AudioRenderThreadCommand([this, MixerSubmixPtr, SoundEffect]()
{
MixerSubmixPtr->AddSoundEffectSubmix(SoundEffect);
});
return ++NumEffects;
}
else
{
UE_LOG(LogAudio, Warning, TEXT("Submix instance %s not found."), *InSoundSubmix->GetName());
}
return 0;
}
void FMixerDevice::RemoveSubmixEffect(USoundSubmix* InSoundSubmix, uint32 SubmixEffectId)
{
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, SubmixEffectId]()
{
MixerSubmixPtr->RemoveSoundEffectSubmix(SubmixEffectId);
});
}
}
void FMixerDevice::RemoveSubmixEffectAtIndex(USoundSubmix* InSoundSubmix, int32 SubmixChainIndex)
{
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, SubmixChainIndex]()
{
MixerSubmixPtr->RemoveSoundEffectSubmixAtIndex(SubmixChainIndex);
});
}
}
void FMixerDevice::ReplaceSoundEffectSubmix(USoundSubmix* InSoundSubmix, int32 InSubmixChainIndex, FSoundEffectSubmixPtr SoundEffect)
{
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, InSubmixChainIndex, SoundEffect]()
{
MixerSubmixPtr->ReplaceSoundEffectSubmix(InSubmixChainIndex, SoundEffect);
});
}
}
void FMixerDevice::ClearSubmixEffects(USoundSubmix* InSoundSubmix)
{
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr]()
{
MixerSubmixPtr->ClearSoundEffectSubmixes();
});
}
}
void FMixerDevice::SetSubmixEffectChainOverride(USoundSubmix* InSoundSubmix, const TArray<FSoundEffectSubmixPtr>& InSubmixEffectPresetChain, float InFadeTimeSec)
{
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, InSubmixEffectPresetChain, InFadeTimeSec]()
{
MixerSubmixPtr->SetSubmixEffectChainOverride(InSubmixEffectPresetChain, InFadeTimeSec);
});
}
}
void FMixerDevice::ClearSubmixEffectChainOverride(USoundSubmix* InSoundSubmix, float InFadeTimeSec)
{
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, InFadeTimeSec]()
{
MixerSubmixPtr->ClearSubmixEffectChainOverride(InFadeTimeSec);
});
}
}
void FMixerDevice::UpdateSourceEffectChain(const uint32 SourceEffectChainId, const TArray<FSourceEffectChainEntry>& SourceEffectChain, const bool bPlayEffectChainTails)
{
TArray<FSourceEffectChainEntry>* ExistingOverride = SourceEffectChainOverrides.Find(SourceEffectChainId);
if (ExistingOverride)
{
*ExistingOverride = SourceEffectChain;
}
else
{
SourceEffectChainOverrides.Add(SourceEffectChainId, SourceEffectChain);
}
FAudioThread::RunCommandOnAudioThread([MixerDeviceID = DeviceID, SourceEffectChainId, SourceEffectChain, bPlayEffectChainTails]()
{
if (FAudioDeviceManager* Manager = FAudioDeviceManager::Get())
{
if (FAudioDevice* Device = Manager->GetAudioDeviceRaw(MixerDeviceID))
{
FMixerDevice* MixerDevice = static_cast<FMixerDevice*>(Device);
if (MixerDevice && MixerDevice->SourceManager)
{
MixerDevice->SourceManager->UpdateSourceEffectChain(SourceEffectChainId, SourceEffectChain, bPlayEffectChainTails);
}
}
}
});
}
void FMixerDevice::UpdateSubmixProperties(USoundSubmixBase* InSoundSubmix)
{
check(InSoundSubmix);
// Output volume is only supported on USoundSubmixes.
USoundSubmix* CastedSubmix = Cast<USoundSubmix>(InSoundSubmix);
if (!CastedSubmix)
{
return;
}
#if WITH_EDITOR
check(IsInAudioThread());
FMixerSubmixPtr MixerSubmix = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmix.IsValid())
{
const float NewVolume = CastedSubmix->OutputVolumeModulation.Value;
AudioRenderThreadCommand([MixerSubmix, NewVolume]()
{
MixerSubmix->SetOutputVolume(NewVolume);
});
}
#endif // WITH_EDITOR
}
void FMixerDevice::SetSubmixWetDryLevel(USoundSubmix* InSoundSubmix, float InOutputVolume, float InWetLevel, float InDryLevel)
{
if (!IsInAudioThread())
{
FMixerDevice* MixerDevice = this;
FAudioThread::RunCommandOnAudioThread([MixerDevice, InSoundSubmix, InOutputVolume, InWetLevel, InDryLevel]()
{
MixerDevice->SetSubmixWetDryLevel(InSoundSubmix, InOutputVolume, InWetLevel, InDryLevel);
});
return;
}
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, InOutputVolume, InWetLevel, InDryLevel]()
{
MixerSubmixPtr->SetOutputVolume(InOutputVolume);
MixerSubmixPtr->SetWetLevel(InWetLevel);
MixerSubmixPtr->SetDryLevel(InDryLevel);
});
}
}
void FMixerDevice::SetSubmixOutputVolume(USoundSubmix* InSoundSubmix, float InOutputVolume)
{
if (!IsInAudioThread())
{
FMixerDevice* MixerDevice = this;
FAudioThread::RunCommandOnAudioThread([MixerDevice, InSoundSubmix, InOutputVolume]()
{
MixerDevice->SetSubmixOutputVolume(InSoundSubmix, InOutputVolume);
});
return;
}
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, InOutputVolume]()
{
MixerSubmixPtr->SetOutputVolume(InOutputVolume);
});
}
}
void FMixerDevice::SetSubmixWetLevel(USoundSubmix* InSoundSubmix, float InWetLevel)
{
if (!IsInAudioThread())
{
FMixerDevice* MixerDevice = this;
FAudioThread::RunCommandOnAudioThread([MixerDevice, InSoundSubmix, InWetLevel]()
{
MixerDevice->SetSubmixWetLevel(InSoundSubmix, InWetLevel);
});
return;
}
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, InWetLevel]()
{
MixerSubmixPtr->SetWetLevel(InWetLevel);
});
}
}
void FMixerDevice::SetSubmixDryLevel(USoundSubmix* InSoundSubmix, float InDryLevel)
{
if (!IsInAudioThread())
{
FMixerDevice* MixerDevice = this;
FAudioThread::RunCommandOnAudioThread([MixerDevice, InSoundSubmix, InDryLevel]()
{
MixerDevice->SetSubmixDryLevel(InSoundSubmix, InDryLevel);
});
return;
}
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, InDryLevel]()
{
MixerSubmixPtr->SetDryLevel(InDryLevel);
});
}
}
void FMixerDevice::SetSubmixAutoDisable(USoundSubmix* InSoundSubmix, bool bInAutoDisable)
{
if (!IsInAudioThread())
{
FMixerDevice* MixerDevice = this;
FAudioThread::RunCommandOnAudioThread([MixerDevice, InSoundSubmix, bInAutoDisable]()
{
MixerDevice->SetSubmixAutoDisable(InSoundSubmix, bInAutoDisable);
});
return;
}
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, bInAutoDisable]()
{
MixerSubmixPtr->SetAutoDisable(bInAutoDisable);
});
}
}
void FMixerDevice::SetSubmixAutoDisableTime(USoundSubmix* InSoundSubmix, float InDisableTime)
{
if (!IsInAudioThread())
{
FMixerDevice* MixerDevice = this;
FAudioThread::RunCommandOnAudioThread([MixerDevice, InSoundSubmix, InDisableTime]()
{
MixerDevice->SetSubmixAutoDisableTime(InSoundSubmix, InDisableTime);
});
return;
}
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, InDisableTime]()
{
MixerSubmixPtr->SetAutoDisableTime(InDisableTime);
});
}
}
void FMixerDevice::UpdateSubmixModulationSettings(USoundSubmix* InSoundSubmix, const TSet<TObjectPtr<USoundModulatorBase>>& InOutputModulation, const TSet<TObjectPtr<USoundModulatorBase>>& InWetLevelModulation, const TSet<TObjectPtr<USoundModulatorBase>>& InDryLevelModulation)
{
TWeakObjectPtr<USoundSubmix> SubmixWeakPtr = InSoundSubmix;
if (!IsInAudioThread())
{
FAudioThread::RunCommandOnAudioThread([ThisDeviceID = DeviceID, SubmixWeakPtr, OutMod = InOutputModulation, WetMod = InWetLevelModulation, DryMod = InDryLevelModulation]()
{
if (USoundSubmix* Submix = SubmixWeakPtr.Get())
{
if (FAudioDevice* Device = FAudioDeviceManager::Get()->GetAudioDeviceRaw(ThisDeviceID))
{
FMixerDevice* ThisMixerDevice = static_cast<FMixerDevice*>(Device);
ThisMixerDevice->UpdateSubmixModulationSettings(Submix, OutMod, WetMod, DryMod);
}
}
});
return;
}
if (IsModulationPluginEnabled() && ModulationInterface.IsValid())
{
FMixerSubmixWeakPtr MixerSubmixWeakPtr = GetSubmixInstance(InSoundSubmix);
if (SubmixWeakPtr.IsValid())
{
FMixerSubmixPtr MixerSubmixPtr = MixerSubmixWeakPtr.Pin();
if (MixerSubmixPtr.IsValid())
{
MixerSubmixPtr->UpdateModulationSettings(InOutputModulation, InWetLevelModulation, InDryLevelModulation);
}
}
}
}
void FMixerDevice::SetSubmixModulationBaseLevels(USoundSubmix* InSoundSubmix, float InVolumeModBase, float InWetModBase, float InDryModBase)
{
if (!IsInAudioThread())
{
TWeakObjectPtr<USoundSubmix> SubmixWeakPtr = InSoundSubmix;
FAudioThread::RunCommandOnAudioThread([ThisDeviceID = DeviceID, SubmixWeakPtr, InVolumeModBase, InWetModBase, InDryModBase]()
{
if (USoundSubmix* Submix = SubmixWeakPtr.Get())
{
if (FAudioDevice* Device = FAudioDeviceManager::Get()->GetAudioDeviceRaw(ThisDeviceID))
{
FMixerDevice* ThisMixerDevice = static_cast<FMixerDevice*>(Device);
ThisMixerDevice->SetSubmixModulationBaseLevels(Submix, InVolumeModBase, InWetModBase, InDryModBase);
}
}
});
return;
}
FMixerSubmixPtr MixerSubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (MixerSubmixPtr.IsValid())
{
AudioRenderThreadCommand([MixerSubmixPtr, InVolumeModBase, InWetModBase, InDryModBase]()
{
MixerSubmixPtr->SetModulationBaseLevels(InVolumeModBase, InWetModBase, InDryModBase);
});
}
}
bool FMixerDevice::GetCurrentSourceEffectChain(const uint32 SourceEffectChainId, TArray<FSourceEffectChainEntry>& OutCurrentSourceEffectChainEntries)
{
TArray<FSourceEffectChainEntry>* ExistingOverride = SourceEffectChainOverrides.Find(SourceEffectChainId);
if (ExistingOverride)
{
OutCurrentSourceEffectChainEntries = *ExistingOverride;
return true;
}
return false;
}
void FMixerDevice::AudioRenderThreadCommand(TFunction<void()> Command)
{
CommandQueue.Enqueue(MoveTemp(Command));
}
void FMixerDevice::GameThreadMPSCCommand(TFunction<void()> InCommand)
{
GameThreadCommandQueue.Enqueue(MoveTemp(InCommand));
}
void FMixerDevice::PumpCommandQueue()
{
// Execute the pushed lambda functions
TFunction<void()> Command;
while (CommandQueue.Dequeue(Command))
{
Command();
}
}
void FMixerDevice::PumpGameThreadCommandQueue()
{
TOptional Opt { GameThreadCommandQueue.Dequeue() };
while (Opt.IsSet())
{
TFunction<void()> Command = MoveTemp(Opt.GetValue());
Command();
Opt = GameThreadCommandQueue.Dequeue();
}
}
void FMixerDevice::FlushAudioRenderingCommands(bool bPumpSynchronously)
{
if (IsInitialized() && (FPlatformProcess::SupportsMultithreading() && !AudioMixerPlatform->IsNonRealtime()))
{
SourceManager->FlushCommandQueue(bPumpSynchronously);
}
else if (AudioMixerPlatform->IsNonRealtime())
{
SourceManager->FlushCommandQueue(true);
}
else
{
// Pump the audio device's command queue
PumpCommandQueue();
// And also directly pump the source manager command queue
SourceManager->PumpCommandQueue();
SourceManager->PumpCommandQueue();
SourceManager->UpdatePendingReleaseData(true);
}
}
bool FMixerDevice::IsRequiredSubmixType(const USoundSubmixBase* InSubmix) const
{
for (int32 i = 0; i < (int32)EMasterSubmixType::Count; ++i)
{
if (InSubmix == RequiredSubmixes[i])
{
return true;
}
}
return false;
}
FMixerSubmixPtr FMixerDevice::GetRequiredSubmixInstance(uint32 InObjectId) const
{
check(RequiredSubmixes.Num() == (int32)ERequiredSubmixes::Count);
for (int32 i = 0; i < (int32)ERequiredSubmixes::Count; ++i)
{
if (RequiredSubmixes[i] && InObjectId == RequiredSubmixes[i]->GetUniqueID())
{
return RequiredSubmixInstances[i];
}
}
return nullptr;
}
FMixerSubmixPtr FMixerDevice::GetRequiredSubmixInstance(const USoundSubmixBase* InSubmix) const
{
check(RequiredSubmixes.Num() == (int32)ERequiredSubmixes::Count);
for (int32 i = 0; i < (int32)ERequiredSubmixes::Count; ++i)
{
if (InSubmix == RequiredSubmixes[i])
{
return RequiredSubmixInstances[i];
}
}
return nullptr;
}
void FMixerDevice::RegisterSoundSubmix(USoundSubmixBase* InSoundSubmix, bool bInit)
{
if (InSoundSubmix && bSubmixRegistrationDisabled)
{
UE_LOG(LogAudioMixer, Warning, TEXT("Attempted register Submix %s before the submix graph was initialized."), *InSoundSubmix->GetFullName());
return;
}
if (!InSoundSubmix)
{
return;
}
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.RegisterSoundSubmix"), STAT_AudioRegisterSoundSubmix, STATGROUP_AudioThreadCommands);
FMixerDevice* MixerDevice = this;
FAudioThread::RunCommandOnAudioThread([MixerDevice, InSoundSubmix, bInit]()
{
CSV_SCOPED_TIMING_STAT(Audio, RegisterSubmix);
MixerDevice->RegisterSoundSubmix(InSoundSubmix, bInit);
}, GET_STATID(STAT_AudioRegisterSoundSubmix));
return;
}
UE_LOG(LogAudioMixer, Display, TEXT("Registering submix %s."), *InSoundSubmix->GetFullName());
const bool bIsMainSubmix = IsRequiredSubmixType(InSoundSubmix);
if (!bIsMainSubmix)
{
// Ensure parent structure is registered prior to current submix if missing
if (const USoundSubmixWithParentBase* SubmixWithParent = Cast<const USoundSubmixWithParentBase>(InSoundSubmix))
{
if (TObjectPtr<USoundSubmixBase> Parent = SubmixWithParent->GetParent(DeviceID))
{
FMixerSubmixPtr ParentSubmix = GetSubmixInstance(Parent).Pin();
if (!ParentSubmix.IsValid())
{
RegisterSoundSubmix(Parent, bInit);
}
}
}
LoadSoundSubmix(*InSoundSubmix);
}
else
{
UE_LOG(LogAudioMixer, Display, TEXT("Submix %s was already registered as one of the master submixes."), *InSoundSubmix->GetFullName());
}
FMixerSubmixPtr SubmixPtr = GetSubmixInstance(InSoundSubmix).Pin();
if (bInit)
{
InitSoundfieldAndEndpointDataForSubmix(*InSoundSubmix, SubmixPtr, true);
}
if (!bIsMainSubmix)
{
RebuildSubmixLinks(*InSoundSubmix, SubmixPtr);
}
}
void FMixerDevice::LoadSoundSubmix(USoundSubmixBase& InSoundSubmix)
{
// If submix not already found, load it.
FMixerSubmixPtr MixerSubmix = GetSubmixInstance(&InSoundSubmix).Pin();
if (!MixerSubmix.IsValid())
{
InSoundSubmix.AddToRoot();
MixerSubmix = MakeShared<FMixerSubmix, ESPMode::ThreadSafe>(this);
Submixes.Add(InSoundSubmix.GetUniqueID(), MixerSubmix);
}
}
void FMixerDevice::InitSoundfieldAndEndpointDataForSubmix(const USoundSubmixBase& InSoundSubmix, FMixerSubmixPtr MixerSubmix, bool bAllowReInit)
{
{
FScopeLock ScopeLock(&EndpointSubmixesMutationLock);
// Check to see if this is an endpoint or soundfield submix:
if (const USoundfieldSubmix* SoundfieldSubmix = Cast<const USoundfieldSubmix>(&InSoundSubmix))
{
MixerSubmix->SetSoundfieldFactory(SoundfieldSubmix->GetSoundfieldFactoryForSubmix());
}
else if (const USoundfieldEndpointSubmix* SoundfieldEndpointSubmix = Cast<const USoundfieldEndpointSubmix>(&InSoundSubmix))
{
MixerSubmix->SetSoundfieldFactory(SoundfieldEndpointSubmix->GetSoundfieldEndpointForSubmix());
}
if (DefaultEndpointSubmixes.Contains(MixerSubmix))
{
DefaultEndpointSubmixes.RemoveSwap(MixerSubmix);
}
if (ExternalEndpointSubmixes.Contains(MixerSubmix))
{
ExternalEndpointSubmixes.RemoveSwap(MixerSubmix);
}
MixerSubmix->Init(&InSoundSubmix, bAllowReInit);
if (IsEndpointSubmix(&InSoundSubmix) && MixerSubmix->IsDefaultEndpointSubmix())
{
DefaultEndpointSubmixes.Add(MixerSubmix);
}
else if (MixerSubmix->IsExternalEndpointSubmix())
{
ExternalEndpointSubmixes.Add(MixerSubmix);
}
}
}
void FMixerDevice::UnregisterSoundSubmix(const USoundSubmixBase* InSoundSubmix, const bool bReparentChildren)
{
if (!InSoundSubmix || bSubmixRegistrationDisabled || IsRequiredSubmixType(InSoundSubmix))
{
return;
}
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.UnregisterSoundSubmix"), STAT_AudioUnregisterSoundSubmix, STATGROUP_AudioThreadCommands);
const TWeakObjectPtr<const USoundSubmixBase> SubmixToUnload = InSoundSubmix;
FAudioThread::RunCommandOnAudioThread([this, SubmixToUnload, bReparentChildren]()
{
CSV_SCOPED_TIMING_STAT(Audio, UnregisterSubmix);
if (SubmixToUnload.IsValid())
{
UnloadSoundSubmix(*SubmixToUnload.Get(), bReparentChildren);
}
}, GET_STATID(STAT_AudioUnregisterSoundSubmix));
return;
}
UnloadSoundSubmix(*InSoundSubmix, bReparentChildren);
}
void FMixerDevice::UnloadSoundSubmix(const USoundSubmixBase& InSoundSubmix, const bool bReparentChildren)
{
check(IsInAudioThread());
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
// Check if this is a submix type that has a parent.
FMixerSubmixPtr ParentSubmixInstance;
if (const USoundSubmixWithParentBase* InSoundSubmixWithParent = Cast<const USoundSubmixWithParentBase>(&InSoundSubmix))
{
ParentSubmixInstance = InSoundSubmixWithParent->GetParent(DeviceID)
? GetSubmixInstance(InSoundSubmixWithParent->GetParent(DeviceID)).Pin()
: MainSubmix.Pin();
}
if (ParentSubmixInstance.IsValid())
{
ParentSubmixInstance->RemoveChildSubmix(GetSubmixInstance(&InSoundSubmix));
}
if (bReparentChildren)
{
for (USoundSubmixBase* ChildSubmix : InSoundSubmix.ChildSubmixes)
{
FMixerSubmixPtr ChildSubmixPtr = GetSubmixInstance(ChildSubmix).Pin();
if (ChildSubmixPtr.IsValid())
{
ChildSubmixPtr->SetParentSubmix(ParentSubmixInstance.IsValid()
? ParentSubmixInstance
: MainSubmix);
}
}
}
FMixerSubmixWeakPtr MixerSubmixWeakPtr = GetSubmixInstance(&InSoundSubmix);
FMixerSubmixPtr MixerSubmix = MixerSubmixWeakPtr.Pin();
if (MixerSubmix && MixerSubmix->IsDefaultEndpointSubmix())
{
FScopeLock ScopeLock(&EndpointSubmixesMutationLock);
DefaultEndpointSubmixes.Remove(MixerSubmix);
}
else if (MixerSubmix && MixerSubmix->IsExternalEndpointSubmix())
{
FScopeLock ScopeLock(&EndpointSubmixesMutationLock);
ExternalEndpointSubmixes.Remove(MixerSubmix);
}
Submixes.Remove(InSoundSubmix.GetUniqueID());
}
FMixerSubmixPtr FMixerDevice::FindSubmixInstanceByObjectId(uint32 InObjectId)
{
for (int32 i = 0; i < RequiredSubmixes.Num(); i++)
{
if (const USoundSubmix* MainSubmix = RequiredSubmixes[i])
{
if (MainSubmix->GetUniqueID() == InObjectId)
{
return GetRequiredSubmixInstance(MainSubmix);
}
}
else
{
const ERequiredSubmixes SubmixType = static_cast<ERequiredSubmixes>(i);
ensureAlwaysMsgf(ERequiredSubmixes::Main != SubmixType,
TEXT("Top-level main submix has to be registered before anything else, and is required for the lifetime of the application.")
);
if (!DisableSubmixEffectEQCvar && ERequiredSubmixes::EQ == SubmixType)
{
UE_LOG(LogAudioMixer, Warning, TEXT("Failed to query EQ Submix when it was expected to be loaded."));
}
}
}
return Submixes.FindRef(InObjectId);
}
FMixerSubmixWeakPtr FMixerDevice::GetSubmixInstance(const USoundSubmixBase* SoundSubmix) const
{
LLM_SCOPE(ELLMTag::AudioMixer);
FMixerSubmixPtr MixerSubmix = GetRequiredSubmixInstance(SoundSubmix);
if (MixerSubmix.IsValid())
{
return MixerSubmix;
}
if (SoundSubmix)
{
return Submixes.FindRef(SoundSubmix->GetUniqueID());
}
return nullptr;
}
ISoundfieldFactory* FMixerDevice::GetFactoryForSubmixInstance(USoundSubmix* SoundSubmix)
{
FMixerSubmixWeakPtr WeakSubmixPtr = GetSubmixInstance(SoundSubmix);
return GetFactoryForSubmixInstance(WeakSubmixPtr);
}
ISoundfieldFactory* FMixerDevice::GetFactoryForSubmixInstance(FMixerSubmixWeakPtr& SoundSubmixPtr)
{
FMixerSubmixPtr SubmixPtr = SoundSubmixPtr.Pin();
if (SubmixPtr.IsValid())
{
return SubmixPtr->GetSoundfieldFactory();
}
else
{
return nullptr;
}
}
FMixerSourceVoice* FMixerDevice::GetMixerSourceVoice()
{
LLM_SCOPE(ELLMTag::AudioMixer);
FMixerSourceVoice* Voice = nullptr;
if (!SourceVoices.Dequeue(Voice))
{
Voice = new FMixerSourceVoice();
}
Voice->Reset(this);
return Voice;
}
void FMixerDevice::ReleaseMixerSourceVoice(FMixerSourceVoice* InSourceVoice)
{
SourceVoices.Enqueue(InSourceVoice);
}
int32 FMixerDevice::GetNumSources() const
{
return Sources.Num();
}
IAudioLinkFactory* FMixerDevice::GetAudioLinkFactory() const
{
return AudioLinkFactory;
}
int32 FMixerDevice::GetNumActiveSources() const
{
return SourceManager->GetNumActiveSources();
}
void FMixerDevice::Get3DChannelMap(const int32 InSubmixNumChannels, const FWaveInstance* InWaveInstance, float EmitterAzimith, float InNonSpatializedAmount, const TMap<EAudioMixerChannel::Type, float>* InOmniMap, float InDefaultOmniValue, Audio::FAlignedFloatBuffer& OutChannelMap)
{
// If we're center-channel only, then no need for spatial calculations, but need to build a channel map
if (InWaveInstance->bCenterChannelOnly)
{
int32 NumOutputChannels = InSubmixNumChannels;
const TArray<EAudioMixerChannel::Type>& ChannelArray = GetChannelArray();
// If we are only spatializing to stereo output
if (NumOutputChannels == 2)
{
// Equal volume in left + right channel with equal power panning
static const float Pan = 1.0f / FMath::Sqrt(2.0f);
OutChannelMap.Add(Pan);
OutChannelMap.Add(Pan);
}
else
{
for (EAudioMixerChannel::Type Channel : ChannelArray)
{
float Pan = (Channel == EAudioMixerChannel::FrontCenter) ? 1.0f : 0.0f;
OutChannelMap.Add(Pan);
}
}
return;
}
float Azimuth = EmitterAzimith;
const FChannelPositionInfo* PrevChannelInfo = nullptr;
const FChannelPositionInfo* NextChannelInfo = nullptr;
for (int32 i = 0; i < DeviceChannelAzimuthPositions.Num(); ++i)
{
const FChannelPositionInfo& ChannelPositionInfo = DeviceChannelAzimuthPositions[i];
if (Azimuth <= ChannelPositionInfo.Azimuth)
{
NextChannelInfo = &DeviceChannelAzimuthPositions[i];
int32 PrevIndex = i - 1;
if (PrevIndex < 0)
{
PrevIndex = DeviceChannelAzimuthPositions.Num() - 1;
}
PrevChannelInfo = &DeviceChannelAzimuthPositions[PrevIndex];
break;
}
}
// If we didn't find anything, that means our azimuth position is at the top of the mapping
if (PrevChannelInfo == nullptr)
{
PrevChannelInfo = &DeviceChannelAzimuthPositions[DeviceChannelAzimuthPositions.Num() - 1];
NextChannelInfo = &DeviceChannelAzimuthPositions[0];
AUDIO_MIXER_CHECK(PrevChannelInfo != NextChannelInfo);
}
float NextChannelAzimuth = NextChannelInfo->Azimuth;
float PrevChannelAzimuth = PrevChannelInfo->Azimuth;
if (NextChannelAzimuth < PrevChannelAzimuth)
{
NextChannelAzimuth += 360.0f;
}
if (Azimuth < PrevChannelAzimuth)
{
Azimuth += 360.0f;
}
AUDIO_MIXER_CHECK(NextChannelAzimuth > PrevChannelAzimuth);
AUDIO_MIXER_CHECK(Azimuth > PrevChannelAzimuth);
float Fraction = (Azimuth - PrevChannelAzimuth) / (NextChannelAzimuth - PrevChannelAzimuth);
AUDIO_MIXER_CHECK(Fraction >= 0.0f && Fraction <= 1.0f);
// Compute the panning values using equal-power panning law
float PrevChannelPan;
float NextChannelPan;
if (PanningMethod == EPanningMethod::EqualPower)
{
FMath::SinCos(&NextChannelPan, &PrevChannelPan, Fraction * 0.5f * PI);
// Note that SinCos can return values slightly greater than 1.0 when very close to PI/2
NextChannelPan = FMath::Clamp(NextChannelPan, 0.0f, 1.0f);
PrevChannelPan = FMath::Clamp(PrevChannelPan, 0.0f, 1.0f);
}
else
{
NextChannelPan = Fraction;
PrevChannelPan = 1.0f - Fraction;
}
float OmniAmount = InNonSpatializedAmount;
// Build the output channel map based on the current platform device output channel array
int32 NumSpatialChannels = DeviceChannelAzimuthPositions.Num();
if (DeviceChannelAzimuthPositions.Num() > 4)
{
NumSpatialChannels--;
}
const TArray<EAudioMixerChannel::Type>& ChannelArray = GetChannelArray();
if (OmniAmount > 0.0f)
{
for (EAudioMixerChannel::Type Channel : ChannelArray)
{
float OmniPanFactor = InDefaultOmniValue;
if (InOmniMap)
{
const float* MappedOmniPanFactor = InOmniMap->Find(Channel);
if (MappedOmniPanFactor)
{
OmniPanFactor = *MappedOmniPanFactor;
}
else
{
OmniPanFactor = 0.0f;
}
}
float EffectivePan = 0.0f;
// Check for manual channel mapping parameters (LFE and Front Center)
if (Channel == EAudioMixerChannel::LowFrequency)
{
EffectivePan = InWaveInstance->LFEBleed;
}
else if (Channel == PrevChannelInfo->Channel)
{
EffectivePan = FMath::Lerp(PrevChannelPan, OmniPanFactor, OmniAmount);
}
else if (Channel == NextChannelInfo->Channel)
{
EffectivePan = FMath::Lerp(NextChannelPan, OmniPanFactor, OmniAmount);
}
else if (Channel == EAudioMixerChannel::FrontCenter)
{
EffectivePan = FMath::Lerp(0.0f, OmniPanFactor, OmniAmount);
EffectivePan = FMath::Max(InWaveInstance->VoiceCenterChannelVolume, EffectivePan);
}
else
{
EffectivePan = FMath::Lerp(0.0f, OmniPanFactor, OmniAmount);
}
AUDIO_MIXER_CHECK(EffectivePan >= 0.0f && EffectivePan <= 1.0f);
OutChannelMap.Add(EffectivePan);
}
}
else
{
for (EAudioMixerChannel::Type Channel : ChannelArray)
{
float EffectivePan = 0.0f;
// Check for manual channel mapping parameters (LFE and Front Center)
if (Channel == EAudioMixerChannel::LowFrequency)
{
EffectivePan = InWaveInstance->LFEBleed;
}
else if (Channel == PrevChannelInfo->Channel)
{
EffectivePan = PrevChannelPan;
}
else if (Channel == NextChannelInfo->Channel)
{
EffectivePan = NextChannelPan;
}
else if (Channel == EAudioMixerChannel::FrontCenter)
{
EffectivePan = FMath::Max(InWaveInstance->VoiceCenterChannelVolume, EffectivePan);
}
AUDIO_MIXER_CHECK(EffectivePan >= 0.0f && EffectivePan <= 1.0f);
OutChannelMap.Add(EffectivePan);
}
}
}
const TArray<FTransform>* FMixerDevice::GetListenerTransforms()
{
return SourceManager->GetListenerTransforms();
}
void FMixerDevice::StartRecording(USoundSubmix* InSubmix, float ExpectedRecordingDuration)
{
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.PauseRecording"), STAT_StartRecording, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this, InSubmix, ExpectedRecordingDuration]()
{
CSV_SCOPED_TIMING_STAT(Audio, StartRecording);
StartRecording(InSubmix, ExpectedRecordingDuration);
}, GET_STATID(STAT_StartRecording));
return;
}
// if we can find the submix here, record that submix. Otherwise, just record the master submix.
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
FoundSubmix->OnStartRecordingOutput(ExpectedRecordingDuration);
}
else
{
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
check(MainSubmixPtr.IsValid());
MainSubmixPtr->OnStartRecordingOutput(ExpectedRecordingDuration);
}
}
Audio::FAlignedFloatBuffer& FMixerDevice::StopRecording(USoundSubmix* InSubmix, float& OutNumChannels, float& OutSampleRate)
{
// if we can find the submix here, record that submix. Otherwise, just record the master submix.
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
return FoundSubmix->OnStopRecordingOutput(OutNumChannels, OutSampleRate);
}
else
{
FMixerSubmixPtr MainSubmixPtr = GetMasterSubmix().Pin();
check(MainSubmixPtr.IsValid());
return MainSubmixPtr->OnStopRecordingOutput(OutNumChannels, OutSampleRate);
}
}
void FMixerDevice::PauseRecording(USoundSubmix* InSubmix)
{
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.PauseRecording"), STAT_PauseRecording, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this, InSubmix]()
{
CSV_SCOPED_TIMING_STAT(Audio, PauseRecording);
PauseRecording(InSubmix);
}, GET_STATID(STAT_PauseRecording));
return;
}
// if we can find the submix here, pause that submix. Otherwise, just pause the master submix.
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
FoundSubmix->PauseRecordingOutput();
}
else
{
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
check(MainSubmixPtr.IsValid());
MainSubmixPtr->PauseRecordingOutput();
}
}
void FMixerDevice::ResumeRecording(USoundSubmix* InSubmix)
{
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.ResumeRecording"), STAT_ResumeRecording, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this, InSubmix]()
{
CSV_SCOPED_TIMING_STAT(Audio, ResumeRecording);
ResumeRecording(InSubmix);
}, GET_STATID(STAT_ResumeRecording));
return;
}
// if we can find the submix here, resume that submix. Otherwise, just resume the master submix.
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
FoundSubmix->ResumeRecordingOutput();
}
else
{
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
check(MainSubmixPtr.IsValid());
MainSubmixPtr->ResumeRecordingOutput();
}
}
void FMixerDevice::StartEnvelopeFollowing(USoundSubmix* InSubmix)
{
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.StartEnvelopeFollowing"), STAT_StartEnvelopeFollowing, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this, InSubmix]()
{
CSV_SCOPED_TIMING_STAT(Audio, StartEnvelopeFollowing);
StartEnvelopeFollowing(InSubmix);
}, GET_STATID(STAT_StartEnvelopeFollowing));
return;
}
// if we can find the submix here, record that submix. Otherwise, just record the master submix.
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
FoundSubmix->StartEnvelopeFollowing(InSubmix->EnvelopeFollowerAttackTime, InSubmix->EnvelopeFollowerReleaseTime);
}
else
{
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
check(MainSubmixPtr.IsValid());
MainSubmixPtr->StartEnvelopeFollowing(InSubmix->EnvelopeFollowerAttackTime, InSubmix->EnvelopeFollowerReleaseTime);
}
DelegateBoundSubmixes.AddUnique(InSubmix);
}
void FMixerDevice::StopEnvelopeFollowing(USoundSubmix* InSubmix)
{
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.StopEnvelopeFollowing"), STAT_StopEnvelopeFollowing, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this, InSubmix]()
{
CSV_SCOPED_TIMING_STAT(Audio, StopEnvelopeFollowing);
StopEnvelopeFollowing(InSubmix);
}, GET_STATID(STAT_StopEnvelopeFollowing));
return;
}
// if we can find the submix here, record that submix. Otherwise, just record the master submix.
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
FoundSubmix->StopEnvelopeFollowing();
}
else
{
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
check(MainSubmixPtr.IsValid());
MainSubmixPtr->StopEnvelopeFollowing();
}
DelegateBoundSubmixes.RemoveSingleSwap(InSubmix);
}
void FMixerDevice::AddEnvelopeFollowerDelegate(USoundSubmix* InSubmix, const FOnSubmixEnvelopeBP& OnSubmixEnvelopeBP)
{
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.AddEnvelopeFollowerDelegate"), STAT_AddEnvelopeFollowerDelegate, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this, InSubmix, OnSubmixEnvelopeBP]()
{
CSV_SCOPED_TIMING_STAT(Audio, AddEnvelopeFollowerDelegate);
AddEnvelopeFollowerDelegate(InSubmix, OnSubmixEnvelopeBP);
}, GET_STATID(STAT_AddEnvelopeFollowerDelegate));
return;
}
// if we can find the submix here, record that submix. Otherwise, just record the master submix.
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
FoundSubmix->AddEnvelopeFollowerDelegate(OnSubmixEnvelopeBP);
}
else
{
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
check(MainSubmixPtr.IsValid());
MainSubmixPtr->AddEnvelopeFollowerDelegate(OnSubmixEnvelopeBP);
}
}
void FMixerDevice::RemoveEnvelopeFollowerDelegate(USoundSubmix* InSubmix, const FOnSubmixEnvelopeBP& OnSubmixEnvelopeBP)
{
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.RemoveEnvelopeFollowerDelegate"), STAT_RemoveEnvelopeFollowerDelegate, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this, InSubmix, OnSubmixEnvelopeBP]()
{
CSV_SCOPED_TIMING_STAT(Audio, RemoveEnvelopeFollowerDelegate);
RemoveEnvelopeFollowerDelegate(InSubmix, OnSubmixEnvelopeBP);
}, GET_STATID(STAT_RemoveEnvelopeFollowerDelegate));
return;
}
// Fallback to the master submix if the provided submix isn't found to match behavior from ::AddEnvelopeFollowerDelegate
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
FoundSubmix->RemoveEnvelopeFollowerDelegate(OnSubmixEnvelopeBP);
}
else
{
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
check(MainSubmixPtr.IsValid());
MainSubmixPtr->RemoveEnvelopeFollowerDelegate(OnSubmixEnvelopeBP);
}
}
void FMixerDevice::StartSpectrumAnalysis(USoundSubmix* InSubmix, const FSoundSpectrumAnalyzerSettings& InSettings)
{
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.StartSpectrumAnalysis"), STAT_StartSpectrumAnalysis, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this, InSubmix, InSettings]()
{
CSV_SCOPED_TIMING_STAT(Audio, StartSpectrumAnalysis);
StartSpectrumAnalysis(InSubmix, InSettings);
}, GET_STATID(STAT_StartSpectrumAnalysis));
return;
}
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
FoundSubmix->StartSpectrumAnalysis(InSettings);
}
else
{
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
check(MainSubmixPtr.IsValid());
MainSubmixPtr->StartSpectrumAnalysis(InSettings);
}
DelegateBoundSubmixes.AddUnique(InSubmix);
}
void FMixerDevice::StopSpectrumAnalysis(USoundSubmix* InSubmix)
{
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.StopSpectrumAnalysis"), STAT_StopSpectrumAnalysis, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this, InSubmix]()
{
CSV_SCOPED_TIMING_STAT(Audio, StopSpectrumAnalysis);
StopSpectrumAnalysis(InSubmix);
}, GET_STATID(STAT_StopSpectrumAnalysis));
return;
}
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
FoundSubmix->StopSpectrumAnalysis();
}
else
{
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
check(MainSubmixPtr.IsValid());
MainSubmixPtr->StopSpectrumAnalysis();
}
DelegateBoundSubmixes.RemoveSingleSwap(InSubmix);
}
void FMixerDevice::GetMagnitudesForFrequencies(USoundSubmix* InSubmix, const TArray<float>& InFrequencies, TArray<float>& OutMagnitudes)
{
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
FoundSubmix->GetMagnitudeForFrequencies(InFrequencies, OutMagnitudes);
}
else
{
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
check(MainSubmixPtr.IsValid());
MainSubmixPtr->GetMagnitudeForFrequencies(InFrequencies, OutMagnitudes);
}
}
void FMixerDevice::GetPhasesForFrequencies(USoundSubmix* InSubmix, const TArray<float>& InFrequencies, TArray<float>& OutPhases)
{
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (FoundSubmix.IsValid())
{
FoundSubmix->GetPhaseForFrequencies(InFrequencies, OutPhases);
}
else
{
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FMixerSubmixPtr MainSubmixPtr = MainSubmix.Pin();
check(MainSubmixPtr.IsValid());
MainSubmixPtr->GetPhaseForFrequencies(InFrequencies, OutPhases);
}
}
void FMixerDevice::AddSpectralAnalysisDelegate(USoundSubmix* InSubmix, const FSoundSpectrumAnalyzerDelegateSettings& InDelegateSettings, const FOnSubmixSpectralAnalysisBP& OnSubmixSpectralAnalysisBP)
{
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.AddSpectralAnalysisDelegate"), STAT_AddSpectralAnalysisDelegate, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this, InSubmix, InDelegateSettings, OnSubmixSpectralAnalysisBP]()
{
CSV_SCOPED_TIMING_STAT(Audio, AddSpectralAnalysisDelegate);
AddSpectralAnalysisDelegate(InSubmix, InDelegateSettings, OnSubmixSpectralAnalysisBP);
}, GET_STATID(STAT_AddSpectralAnalysisDelegate));
return;
}
// get submix if it is available.
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (!FoundSubmix.IsValid())
{
// If can't find the submix isntance, use master submix.
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FoundSubmix = MainSubmix.Pin();
}
if (ensure(FoundSubmix.IsValid()))
{
FoundSubmix->AddSpectralAnalysisDelegate(InDelegateSettings, OnSubmixSpectralAnalysisBP);
}
}
void FMixerDevice::RemoveSpectralAnalysisDelegate(USoundSubmix* InSubmix, const FOnSubmixSpectralAnalysisBP& InDelegate)
{
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.RemoveSpectralAnalysisDelegate"), STAT_RemoveSpectralAnalysisDelegate, STATGROUP_AudioThreadCommands);
FAudioThread::RunCommandOnAudioThread([this, InSubmix, InDelegate]()
{
CSV_SCOPED_TIMING_STAT(Audio, RemoveSpectralAnalysisDelegate);
RemoveSpectralAnalysisDelegate(InSubmix, InDelegate);
}, GET_STATID(STAT_RemoveSpectralAnalysisDelegate));
return;
}
// get submix if it is available.
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(InSubmix).Pin();
if (!FoundSubmix.IsValid())
{
// If can't find the submix isntance, use master submix.
FMixerSubmixWeakPtr MainSubmix = GetMasterSubmix();
FoundSubmix = MainSubmix.Pin();
}
if (ensure(FoundSubmix.IsValid()))
{
FoundSubmix->RemoveSpectralAnalysisDelegate(InDelegate);
}
}
USoundSubmix& FMixerDevice::GetMainSubmixObject() const
{
const int32 SubmixIndex = static_cast<int32>(ERequiredSubmixes::Main);
USoundSubmix* MainSubmix = RequiredSubmixes[SubmixIndex];
check(MainSubmix);
return *MainSubmix;
}
void FMixerDevice::RegisterSubmixBufferListener(ISubmixBufferListener* InSubmixBufferListener, USoundSubmix* InSubmix)
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.RegisterSubmixBufferListener"), STAT_RegisterSubmixBufferListener, STATGROUP_AudioThreadCommands);
const bool bUseMaster = InSubmix == nullptr;
const TWeakObjectPtr<USoundSubmix> SubmixPtr(InSubmix);
auto RegisterLambda = [this, InSubmixBufferListener, bUseMaster, SubmixPtr]()
{
CSV_SCOPED_TIMING_STAT(Audio, RegisterSubmixBufferListener);
FMixerSubmixPtr FoundSubmix = bUseMaster
? GetMasterSubmix().Pin()
: GetSubmixInstance(SubmixPtr.Get()).Pin();
// Attempt to register submix if instance not found and is not master (i.e. default) submix
if (!bUseMaster && !FoundSubmix.IsValid() && SubmixPtr.IsValid())
{
RegisterSoundSubmix(SubmixPtr.Get(), true /* bInit */);
FoundSubmix = GetSubmixInstance(SubmixPtr.Get()).Pin();
}
if (FoundSubmix.IsValid())
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
FoundSubmix->RegisterBufferListener(InSubmixBufferListener);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
else
{
UE_LOG(LogAudioMixer, Warning, TEXT("Submix buffer listener not registered. Submix not loaded."));
}
};
FAudioThread::RunCommandOnAudioThread(MoveTemp(RegisterLambda));
}
void FMixerDevice::RegisterSubmixBufferListener(TSharedRef<ISubmixBufferListener, ESPMode::ThreadSafe> InSubmixBufferListener, USoundSubmix& InSubmix)
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.RegisterSubmixBufferListener"), STAT_RegisterSubmixBufferListener, STATGROUP_AudioThreadCommands);
const TWeakObjectPtr<USoundSubmix> SubmixPtr(&InSubmix);
// Pass the name vs. reconciling it inline with the command lambda, as occasionally
// deprecated submix buffer listeners are not constructed as a shared pointer and
// therefore getting destroyed before the lambda is executed on the AudioThread.
// This means no name is provided and thus not apparent who the caller is. If
// the Buffer Listener is invalid here, at least the callstack will show the
// requesting client directly.
const FString ListenerName = InSubmixBufferListener->GetListenerName();
auto RegisterLambda = [this, InSubmixBufferListener, ListenerName, SubmixPtr]()
{
CSV_SCOPED_TIMING_STAT(Audio, RegisterSubmixBufferListener);
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(SubmixPtr.Get()).Pin();
// Attempt to register submix if instance not found and is not master (i.e. default) submix
if (!FoundSubmix.IsValid() && SubmixPtr.IsValid())
{
RegisterSoundSubmix(SubmixPtr.Get(), true /* bInit */);
FoundSubmix = GetSubmixInstance(SubmixPtr.Get()).Pin();
}
if (FoundSubmix.IsValid())
{
FoundSubmix->RegisterBufferListener(InSubmixBufferListener);
UE_LOG(LogAudioMixer, Display, TEXT("Submix buffer listener '%s' registered with submix '%s'"), *ListenerName, *FoundSubmix->SubmixName);
}
else
{
UE_LOG(LogAudioMixer, Warning, TEXT("Submix buffer listener '%s' not registered. Submix not loaded."), *ListenerName);
}
};
UE_LOG(LogAudioMixer, Display, TEXT("Sending SubmixBufferListener '%s' register command..."), *ListenerName);
FAudioThread::RunCommandOnAudioThread(MoveTemp(RegisterLambda));
}
void FMixerDevice::UnregisterSubmixBufferListener(ISubmixBufferListener* InSubmixBufferListener, USoundSubmix* InSubmix)
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.UnregisterSubmixBufferListener"), STAT_UnregisterSubmixBufferListener, STATGROUP_AudioThreadCommands);
const bool bUseMaster = InSubmix == nullptr;
const TWeakObjectPtr<USoundSubmix> SubmixPtr(InSubmix);
auto UnregisterLambda = [this, InSubmixBufferListener, bUseMaster, SubmixPtr]()
{
CSV_SCOPED_TIMING_STAT(Audio, UnregisterSubmixBufferListener);
FMixerSubmixPtr FoundSubmix = bUseMaster
? GetMasterSubmix().Pin()
: GetSubmixInstance(SubmixPtr.Get()).Pin();
if (FoundSubmix.IsValid())
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
FoundSubmix->UnregisterBufferListener(InSubmixBufferListener);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
else
{
UE_LOG(LogAudioMixer, Display, TEXT("Submix buffer listener not unregistered. Submix not loaded."));
}
};
FAudioThread::RunCommandOnAudioThread(MoveTemp(UnregisterLambda));
}
void FMixerDevice::UnregisterSubmixBufferListener(TSharedRef<ISubmixBufferListener, ESPMode::ThreadSafe> InSubmixBufferListener, USoundSubmix& InSubmix)
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.UnregisterSubmixBufferListener"), STAT_UnregisterSubmixBufferListener, STATGROUP_AudioThreadCommands);
const TWeakObjectPtr<USoundSubmix> SubmixPtr(&InSubmix);
UPTRINT ListenerPtr = reinterpret_cast<UPTRINT>(&InSubmixBufferListener.Get());
FString ListenerName = InSubmixBufferListener->GetListenerName();
auto UnregisterLambda = [this, SubmixPtr, ListenerPtr, ListenerName]()
{
CSV_SCOPED_TIMING_STAT(Audio, UnregisterSubmixBufferListener);
FMixerSubmixPtr FoundSubmix = GetSubmixInstance(SubmixPtr.Get()).Pin();
if (FoundSubmix.IsValid())
{
UE_LOG(LogAudioMixer, Display, TEXT("Unregistering submix buffer listener '%s' from submix '%s'"), *ListenerName, *FoundSubmix->SubmixName);
FoundSubmix->UnregisterBufferListenerInternal(ListenerPtr);
}
else
{
UE_LOG(LogAudioMixer, Display, TEXT("Submix buffer listener '%s' not unregistered. Submix not loaded."), *ListenerName);
}
};
FAudioThread::RunCommandOnAudioThread(MoveTemp(UnregisterLambda));
}
void FMixerDevice::FlushExtended(UWorld* WorldToFlush, bool bClearActivatedReverb)
{
QuantizedEventClockManager.Flush();
}
FPatchOutputStrongPtr FMixerDevice::MakePatch(int32 InFrames, int32 InChannels, float InGain) const
{
// Assume the mixer will consume SourceManager->GetNumOutputFrames() per iteration and an input patch will generate InFrames per iteration.
// An input patch must have adequate space to contain as many frames as the mixer might consume, as well as as many as might be pushed to the patch.
// This should be twice the ceiling of the ratio of the larger number of frames to the smaller number, times InFrames.
// An output patch must have adequate space to contain as many frames as the mixer might generate, as well as as many as might be consumed from the patch.
// This should be the same number.
int32 MaxSizeFrames = FMath::Max(InFrames, SourceManager->GetNumOutputFrames()), MinSizeFrames = FMath::Min(InFrames, SourceManager->GetNumOutputFrames());
return MakeShared<Audio::FPatchOutput, ESPMode::ThreadSafe>(AudioMixerPatchBufferBlocks * InFrames * FMath::DivideAndRoundUp(MaxSizeFrames, MinSizeFrames) * InChannels, InGain);
}
FPatchOutputStrongPtr FMixerDevice::AddPatchForSubmix(uint32 InObjectId, float InPatchGain)
{
if (!ensure(IsAudioRenderingThread()))
{
return nullptr;
}
FMixerSubmixPtr SubmixPtr = FindSubmixInstanceByObjectId(InObjectId);
if (SubmixPtr.IsValid())
{
return SubmixPtr->AddPatch(InPatchGain);
}
return nullptr;
}
int32 FMixerDevice::GetDeviceSampleRate() const
{
return SampleRate;
}
int32 FMixerDevice::GetDeviceOutputChannels() const
{
return PlatformInfo.NumChannels;
}
FMixerSourceManager* FMixerDevice::GetSourceManager()
{
return SourceManager.Get();
}
const FMixerSourceManager* FMixerDevice::GetSourceManager() const
{
return SourceManager.Get();
}
bool FMixerDevice::IsMainAudioDevice() const
{
bool bIsMain = (this == FAudioDeviceManager::Get()->GetMainAudioDeviceRaw());
return bIsMain;
}
void FMixerDevice::WhiteNoiseTest(FAlignedFloatBuffer& Output)
{
const int32 NumFrames = OpenStreamParams.NumFrames;
const int32 NumChannels = PlatformInfo.NumChannels;
static FWhiteNoise WhiteNoise;
for (int32 FrameIndex = 0; FrameIndex < NumFrames; ++FrameIndex)
{
for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ++ChannelIndex)
{
int32 Index = FrameIndex * NumChannels + ChannelIndex;
Output[Index] += WhiteNoise.Generate(DebugGeneratorAmpCVar, 0.f);
}
}
}
void FMixerDevice::SineOscTest(FAlignedFloatBuffer& Output)
{
const int32 NumFrames = OpenStreamParams.NumFrames;
const int32 NumChannels = PlatformInfo.NumChannels;
check(NumChannels > 0);
// Constrain user setting if channel index not supported
const int32 ChannelIndex = FMath::Clamp(DebugGeneratorChannelCVar, 0, NumChannels - 1);
static FSineOsc SineOscLeft(PlatformInfo.SampleRate, DebugGeneratorFreqCVar, DebugGeneratorAmpCVar);
static FSineOsc SineOscRight(PlatformInfo.SampleRate, DebugGeneratorFreqCVar / 2.0f, DebugGeneratorAmpCVar);
SineOscLeft.SetFrequency(DebugGeneratorFreqCVar);
SineOscLeft.SetScale(DebugGeneratorAmpCVar);
if (!DebugGeneratorEnableCVar)
{
SineOscRight.SetFrequency(DebugGeneratorFreqCVar / 2.0f);
SineOscRight.SetScale(DebugGeneratorAmpCVar);
}
for (int32 FrameIndex = 0; FrameIndex < NumFrames; ++FrameIndex)
{
int32 Index = FrameIndex * NumChannels;
Output[Index + ChannelIndex] += SineOscLeft.ProcessAudio();
// Using au. commands for debug only supports discrete channel
if (!DebugGeneratorEnableCVar)
{
if (NumChannels > 1 && DebugGeneratorChannelCVar == 0)
{
Output[Index + 1] += SineOscRight.ProcessAudio();
}
}
}
}
void FMixerDevice::CreateSynchronizedAudioTaskQueue(AudioTaskQueueId QueueId)
{
Audio::CreateSynchronizedAudioTaskQueue(QueueId);
}
void FMixerDevice::DestroySynchronizedAudioTaskQueue(AudioTaskQueueId QueueId, bool RunCurrentQueue)
{
Audio::DestroySynchronizedAudioTaskQueue(QueueId, RunCurrentQueue);
}
int FMixerDevice::KickQueuedTasks(AudioTaskQueueId QueueId)
{
return Audio::KickQueuedTasks(QueueId);
}
double FAudioClockTimingData::GetInterpolatedAudioClock(const double InAudioClock, const double InAudioClockDelta) const
{
if (UpdateTime > 0.0)
{
const double TargetClock = InAudioClock + InAudioClockDelta;
const double CurrentDeltaSeconds = FPlatformTime::Seconds() - UpdateTime;
const double Alpha = FMath::Clamp(CurrentDeltaSeconds / InAudioClockDelta, 0.0f, 1.0f);
const double InterpolatedClock = FMath::Lerp(InAudioClock, TargetClock, Alpha);
return InterpolatedClock;
}
// Fall back to quantized clock if no timing data is available
return InAudioClock;
}
}