Files
UnrealEngineUWP/Engine/Source/Runtime/Engine/Private/AudioDebug.cpp
anna lantz 5545191476 Making an au.debug version of stat streaming
#jira UE-147851
#rb ryan.mangin, rob.gay
#preflight 6272d3dcdc361302274fb01f
#p4v-cherrypick 20047090

#ROBOMERGE-OWNER: anna.lantz
#ROBOMERGE-AUTHOR: anna.lantz
#ROBOMERGE-SOURCE: CL 20067757 via CL 20067973 via CL 20067976
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v943-19904690)

[CL 20068293 by anna lantz in ue5-main branch]
2022-05-05 22:19:28 -04:00

2465 lines
82 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Audio/AudioDebug.h"
#include "ActiveSound.h"
#include "Audio.h"
#include "AudioDevice.h"
#include "AudioDeviceManager.h"
#include "AudioEffect.h"
#include "AudioVirtualLoop.h"
#include "CanvasTypes.h"
#include "Components/AudioComponent.h"
#include "DrawDebugHelpers.h"
#include "DSP/Dsp.h"
#include "Engine/Font.h"
#include "Engine/World.h"
#include "GameFramework/GameUserSettings.h"
#include "HAL/IConsoleManager.h"
#include "IAudioModulation.h"
#include "Misc/CommandLine.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/Parse.h"
#include "Serialization/Archive.h"
#include "Sound/AudioSettings.h"
#include "Sound/AudioVolume.h"
#include "Sound/ReverbEffect.h"
#include "Sound/SoundAttenuation.h"
#include "Sound/SoundClass.h"
#include "Sound/SoundConcurrency.h"
#include "Sound/SoundCue.h"
#include "Sound/SoundNodeWavePlayer.h"
#include "Sound/SoundMix.h"
#include "Sound/SoundSourceBus.h"
#include "Sound/SoundWave.h"
#include "UnrealEngine.h"
#if WITH_EDITOR
#include "Editor.h"
#include "Engine/GameViewportClient.h"
#include "LevelEditorViewport.h"
#include "UnrealClient.h"
#endif // WITH_EDITOR
#ifndef ENABLE_AUDIO_DEBUG
#error "Please define ENABLE_AUDIO_DEBUG"
#endif //ENABLE_AUDIO_DEBUG
#if ENABLE_AUDIO_DEBUG
// Console variables
static int32 ActiveSoundVisualizeModeCVar = 1;
FAutoConsoleVariableRef CVarAudioVisualizeActiveSoundsMode(
TEXT("au.3dVisualize.ActiveSounds"),
ActiveSoundVisualizeModeCVar,
TEXT("Visualization mode for active sounds. \n")
TEXT("0: Not Enabled, 1: Volume (Lin), 2: Volume (dB), 3: Distance, 4: Random color, 5: Occlusion"),
ECVF_Default);
static int32 ActiveSoundVisualizeListenersCVar = 0;
FAutoConsoleVariableRef CVarAudioVisualizeListeners(
TEXT("au.3dVisualize.Listeners"),
ActiveSoundVisualizeListenersCVar,
TEXT("Whether or not listeners are visible when 3d visualize is enabled. \n")
TEXT("0: Not Enabled, 1: Enabled"),
ECVF_Default);
static int32 ActiveSoundVisualizeTypeCVar = 0;
FAutoConsoleVariableRef CVarAudioVisualizeActiveSounds(
TEXT("au.3dVisualize.ActiveSounds.Type"),
ActiveSoundVisualizeTypeCVar,
TEXT("Whether to show all sounds, on AudioComponents (Components Only), or off of AudioComponents (Non-Component Only). \n")
TEXT("0: All, 1: Components Only, 2: Non-Component Only"),
ECVF_Default);
static int32 SpatialSourceVisualizeEnabledCVar = 1;
FAutoConsoleVariableRef CVarAudioVisualizeSpatialSourceEnabled(
TEXT("au.3dVisualize.SpatialSources"),
SpatialSourceVisualizeEnabledCVar,
TEXT("Whether or not audio spatialized sources are visible when 3d visualize is enabled. \n")
TEXT("0: Not Enabled, 1: Enabled"),
ECVF_Default);
static int32 VirtualLoopsVisualizeEnabledCVar = 1;
FAutoConsoleVariableRef CVarAudioVisualizeVirtualLoopsEnabled(
TEXT("au.3dVisualize.VirtualLoops"),
VirtualLoopsVisualizeEnabledCVar,
TEXT("Whether or not virtualized loops are visible when 3d visualize is enabled. \n")
TEXT("0: Not Enabled, 1: Enabled"),
ECVF_Default);
static int32 AudioDebugSoundMaxNumDisplayedCVar = 32;
FAutoConsoleVariableRef CVarAudioDebugSoundMaxNumDisplayed(
TEXT("au.Debug.Sounds.Max"),
AudioDebugSoundMaxNumDisplayedCVar,
TEXT("Max number of sounds to display in full sound debugger view. \n")
TEXT("Default: 32"),
ECVF_Default);
static int32 AudioDebugSoundShowPathCVar = 1;
FAutoConsoleVariableRef CVarAudioDebugSoundShowPath(
TEXT("au.Debug.Sounds.ShowPath"),
AudioDebugSoundShowPathCVar,
TEXT("Display full path of sound when enabled.\n")
TEXT("0: Not Enabled, 1: Enabled"),
ECVF_Default);
static FString AudioDebugSoundSortCVarCVar = TEXT("Name");
FAutoConsoleVariableRef CVarAudioDebugSoundSortCVar(
TEXT("au.Debug.Sounds.Sort"),
AudioDebugSoundSortCVarCVar,
TEXT("Value to sort by and display when sound stats are active. \n")
TEXT("Class, Distance, Name (Default), Priority (Highest of wave instances per sound), Time, Waves, Volume"),
ECVF_Default);
static FString AudioDebugStatSoundTextColorCVar = TEXT("White");
FAutoConsoleVariableRef CVarAudioDebugStatSoundColor(
TEXT("au.Debug.Sounds.TextColor"),
AudioDebugStatSoundTextColorCVar,
TEXT("Color of body text in audio debug views. \n")
TEXT("White, Red, Orange, Yellow, Blue, Magenta, Purple, Black"),
ECVF_Default);
static int32 SoundCueDebugShowPathCVar = 1;
FAutoConsoleVariableRef CVarAudioSoundCueDebugShowPath(
TEXT("au.Debug.Soundcues.ShowPath"),
SoundCueDebugShowPathCVar,
TEXT("Display full path of sound cue when enabled.\n")
TEXT("0: Not Enabled, 1: Enabled"),
ECVF_Default);
static int32 SoundCueDebugShowDistanceCVar = 0;
FAutoConsoleVariableRef CVarAudioSoundCueDebugShowDistance(
TEXT("au.Debug.Soundcues.ShowDistance"),
SoundCueDebugShowDistanceCVar,
TEXT("Display distance of sound cue when enabled.\n")
TEXT("0: Not Enabled, 1: Enabled"),
ECVF_Default);
static int32 SoundCueDebugMinimalCVar = 0;
FAutoConsoleVariableRef CVarAudioSoundCueDebugMinimal(
TEXT("au.Debug.SoundCues.Minimal"),
SoundCueDebugMinimalCVar,
TEXT("Use the compact view of sound cue debug when enabled. \n")
TEXT("0: Not Enabled, 1: Enabled"),
ECVF_Default);
static int32 SoundCueDebugTabSpacingCVar = 5;
FAutoConsoleVariableRef CVarAudioSoundCueDebugTabSpacing(
TEXT("au.Debug.SoundCues.Spacing.Tab"),
SoundCueDebugTabSpacingCVar,
TEXT("Size of tab (in characters) with compact view. \n")
TEXT("Default: 5"),
ECVF_Default);
static int32 SoundCueDebugCharSpacingCVar = 7;
FAutoConsoleVariableRef CVarSoundCueDebugCharSpacing(
TEXT("au.Debug.SoundCues.Spacing.Char"),
SoundCueDebugCharSpacingCVar,
TEXT("Size of character (in pixels) with compact view. \n")
TEXT("Default: 7"),
ECVF_Default);
static int32 SoundDebugDisplayCornerXCVar = 100;
FAutoConsoleVariableRef CVarSoundDebugDisplayCornerX(
TEXT("au.Debug.Display.X"),
SoundDebugDisplayCornerXCVar,
TEXT("X position on screen of debug statistics. \n")
TEXT("Default: 100"),
ECVF_Default);
static int32 SoundDebugDisplayCornerYCVar = -1;
FAutoConsoleVariableRef CVarSoundDebugDisplayCornerY(
TEXT("au.Debug.Display.Y"),
SoundDebugDisplayCornerYCVar,
TEXT("X position on screen of debug statistics. \n")
TEXT("Default: -1 (Disabled, uses default debug position)"),
ECVF_Default);
namespace Audio
{
const FColor HeaderColor = FColor::Green;
FColor GetBodyColor()
{
return FColor::White;
}
FColor GetStatSoundBodyColor()
{
if (AudioDebugStatSoundTextColorCVar == TEXT("Red"))
{
return FColor::Red;
}
if (AudioDebugStatSoundTextColorCVar == TEXT("Orange"))
{
return FColor::Orange;
}
if (AudioDebugStatSoundTextColorCVar == TEXT("Yellow"))
{
return FColor::Yellow;
}
if (AudioDebugStatSoundTextColorCVar == TEXT("Green"))
{
return FColor::Green;
}
if (AudioDebugStatSoundTextColorCVar == TEXT("Blue"))
{
return FColor::Blue;
}
if (AudioDebugStatSoundTextColorCVar == TEXT("Magenta"))
{
return FColor::Magenta;
}
if (AudioDebugStatSoundTextColorCVar == TEXT("Purple"))
{
return FColor::Purple;
}
if (AudioDebugStatSoundTextColorCVar == TEXT("Black"))
{
return FColor::Black;
}
return FColor::White;
}
// Whether or not respective stat data is active for any audio device (set on game thread)
static bool bDebugWavesForAllViewsEnabled = false;
static bool bDebugCuesForAllViewsEnabled = false;
static bool bDebugSoundsForAllViewsEnabled = false;
static bool bDebugMixesForAllViewsEnabled = false;
static bool bDebugReverbForAllViewsEnabled = false;
static bool bDebugModulationForAllViewsEnabled = false;
static bool bDebugStreamingForAllViewsEnabled = false;
const int32 DebuggerTabWidth = 12;
const float MinDisplayVolume = UE_KINDA_SMALL_NUMBER; // -80 dB
FAudioDevice* GetWorldAudio(UWorld* World)
{
check(IsInGameThread());
if (!World)
{
return nullptr;
}
return World->GetAudioDeviceRaw();
}
namespace DebugStatNames
{
const FName SoundWaves = "SoundWaves";
const FName SoundCues = "SoundCues";
const FName Sounds = "Sounds";
const FName SoundMixes = "SoundMixes";
const FName SoundModulation = "SoundModulation";
const FName SoundReverb = "SoundReverb";
const FName AudioStreaming = "AudioStreaming";
// TODO: Move to console variables
const FName DebugSounds = "DebugSounds";
const FName LongSoundNames = "LogSoundNames";
}
struct FAudioStats
{
enum class EDisplaySort : uint8
{
Class,
Distance,
Name,
PlaybackTime,
Priority,
Waves,
Volume
};
enum class EDisplayFlags : uint8
{
Debug = 0x01,
Long_Names = 0x40,
};
struct FStatWaveInstanceInfo
{
TSharedPtr<FSoundSource::FDebugInfo, ESPMode::ThreadSafe> DebugInfo;
FString Description;
float Volume;
int32 InstanceIndex;
FName WaveInstanceName;
FName SoundClassName;
uint8 bPlayWhenSilent : 1;
};
struct FStatSoundInfo
{
FString SoundName;
FString SoundPath;
FName SoundClassName;
float Distance;
float PlaybackTime;
float PlaybackTimeNonVirtualized;
float Priority;
float Volume;
uint32 AudioComponentID;
FTransform Transform;
TArray<FStatWaveInstanceInfo> WaveInstanceInfos;
TMultiMap<EAttenuationShape::Type, FBaseAttenuationSettings::AttenuationShapeDetails> ShapeDetailsMap;
};
struct FStatSoundMix
{
FString MixName;
float InterpValue;
int32 RefCount;
bool bIsCurrentEQ;
};
uint8 DisplayFlags;
EDisplaySort DisplaySort;
TSet<FName> EnabledStats;
TArray<FTransform> ListenerTransforms;
TArray<FStatSoundInfo> StatSoundInfos;
TArray<FStatSoundMix> StatSoundMixes;
FAudioStats()
: DisplayFlags(0)
, DisplaySort(EDisplaySort::Name)
, EnabledStats()
{
}
};
void UpdateDisplaySort(FAudioStats& InAudioStats)
{
if (AudioDebugSoundSortCVarCVar == TEXT("distance"))
{
InAudioStats.DisplaySort = FAudioStats::EDisplaySort::Distance;
}
else if (AudioDebugSoundSortCVarCVar == TEXT("class"))
{
InAudioStats.DisplaySort = FAudioStats::EDisplaySort::Class;
}
else if (AudioDebugSoundSortCVarCVar == TEXT("name"))
{
InAudioStats.DisplaySort = FAudioStats::EDisplaySort::Name;
}
else if (AudioDebugSoundSortCVarCVar == TEXT("time"))
{
InAudioStats.DisplaySort = FAudioStats::EDisplaySort::PlaybackTime;
}
else if (AudioDebugSoundSortCVarCVar == TEXT("priority"))
{
InAudioStats.DisplaySort = FAudioStats::EDisplaySort::Priority;
}
else if (AudioDebugSoundSortCVarCVar == TEXT("volume"))
{
InAudioStats.DisplaySort = FAudioStats::EDisplaySort::Volume;
}
else if (AudioDebugSoundSortCVarCVar == TEXT("waves"))
{
InAudioStats.DisplaySort = FAudioStats::EDisplaySort::Waves;
}
}
struct FAudioStats_AudioThread
{
TSet<FName> RequestedStats;
FAudioStats_AudioThread()
: RequestedStats()
{
}
};
TMap<FDeviceId, FAudioStats> AudioDeviceStats;
TMap<FDeviceId, FAudioStats_AudioThread> AudioDeviceStats_AudioThread;
void HandleDumpActiveSounds(UWorld* World)
{
if (GEngine && GEngine->GetAudioDeviceManager())
{
GEngine->GetAudioDeviceManager()->GetDebugger().DumpActiveSounds();
}
}
void HandleClearMutesAndSolos(UWorld* World)
{
if (GEngine && GEngine->GetAudioDeviceManager())
{
GEngine->GetAudioDeviceManager()->GetDebugger().ClearMutesAndSolos();
}
}
template <typename SoundType>
void PlayDebugSound(const TCHAR* Cmd, FAudioDevice& InAudioDevice, UWorld& InWorld, TUniqueFunction<void(SoundType& /*InSound*/)> InInitFunction)
{
UAudioComponent* TestComp = InAudioDevice.GetTestComponent(&InWorld);
if (!TestComp)
{
return;
}
bool bAssetPathSet = false;
FString AssetPath;
if (FParse::Value(Cmd, TEXT("Name"), AssetPath))
{
if (const UAudioSettings* AudioSettings = GetDefault<UAudioSettings>())
{
const FName SoundName(*AssetPath);
for (const FSoundDebugEntry& DebugSound : AudioSettings->DebugSounds)
{
const FSoftObjectPath& ObjectPath = DebugSound.Sound;
if (DebugSound.DebugName == SoundName && ObjectPath.IsValid())
{
AssetPath = ObjectPath.ToString();
bAssetPathSet = true;
break;
}
}
}
}
if (!bAssetPathSet)
{
bAssetPathSet = FParse::Value(Cmd, TEXT("Path"), AssetPath);
}
if (!bAssetPathSet)
{
TArray<FString> Tokens;
FString CmdStr = Cmd;
CmdStr.ParseIntoArray(Tokens, TEXT(" "));
if (!Tokens.Num())
{
return;
}
AssetPath = Tokens[0];
}
// Load up an arbitrary cue
SoundType* SoundObject = LoadObject<SoundType>(nullptr, *AssetPath, nullptr, LOAD_None, nullptr);
if (!SoundObject)
{
return;
}
TestComp->Stop();
TestComp->Sound = SoundObject;
TestComp->bAutoDestroy = true;
float Radius = 1.0f;
float Azimuth = 0.0f;
float Elevation = 0.0f;
TestComp->bAllowSpatialization |= FParse::Value(Cmd, TEXT("Radius"), Radius);
TestComp->bAllowSpatialization |= FParse::Value(Cmd, TEXT("Azimuth"), Azimuth);
TestComp->bAllowSpatialization |= FParse::Value(Cmd, TEXT("Elevation"), Elevation);
if (TestComp->bAllowSpatialization)
{
TestComp->bAllowSpatialization = true;
FTransform TestTransform;
InAudioDevice.GetListenerTransform(0, TestTransform);
static const float AziOffset = 90.0f;
static const float ElevOffset = 90.0f;
const FVector EulerAngles = TestTransform.GetRotation().Euler();
Azimuth = FMath::DegreesToRadians(-1.0f * (EulerAngles.Z + Azimuth + AziOffset));
Elevation = FMath::DegreesToRadians(Elevation + EulerAngles.Y - ElevOffset);
const float X = Radius * FMath::Sin(Elevation) * FMath::Sin(Azimuth);
const float Y = Radius * FMath::Sin(Elevation) * FMath::Cos(Azimuth);
const float Z = Radius * FMath::Cos(Elevation);
const FVector Translation(X, Y, Z);
TestTransform.AddToTranslation(Translation);
TestComp->SetComponentToWorld(TestTransform);
}
InInitFunction(*SoundObject);
TestComp->Play();
}
void HandlePlayDebugSoundCue(const TArray<FString>& InArgs, UWorld* World)
{
if (!World)
{
return;
}
auto PlayDebugSoundCue = [](const TCHAR * InCmd, FAudioDevice & InAudioDevice, UWorld & InWorld)
{
bool bSetLooping = false;
PlayDebugSound<USoundCue>(InCmd, InAudioDevice, InWorld, [InCmd](USoundCue& InCue)
{
TArray<USoundNodeWavePlayer*> WavePlayers;
InCue.RecursiveFindNode<USoundNodeWavePlayer>(InCue.FirstNode, WavePlayers);
for (int32 i = 0; i < WavePlayers.Num(); ++i)
{
if (USoundWave* SoundWave = WavePlayers[i]->GetSoundWave())
{
FAudioDebugger::LogSubtitle(InCmd, *SoundWave);
}
}
});
};
FString Cmd = FString::Join(InArgs, TEXT(" "));
if (FParse::Param(*Cmd, TEXT("AllViews")))
{
if (FAudioDeviceManager* DeviceManager = FAudioDeviceManager::Get())
{
DeviceManager->IterateOverAllDevices([PlayDebugSoundCue, Cmd, World](Audio::FDeviceId, FAudioDevice* AudioDevice)
{
if (AudioDevice)
{
PlayDebugSoundCue(*Cmd, *AudioDevice, *World);
}
});
}
}
else
{
if (FAudioDevice* AudioDevice = World->GetAudioDeviceRaw())
{
PlayDebugSoundCue(*Cmd, *AudioDevice, *World);
}
}
}
void HandlePlayDebugSoundWave(const TArray<FString>& InArgs, UWorld* World)
{
if (!World)
{
return;
}
FString Cmd = FString::Join(InArgs, TEXT(" "));
auto PlayDebugSoundWave = [](const TCHAR * InCmd, FAudioDevice & InAudioDevice, UWorld & InWorld)
{
PlayDebugSound<USoundWave>(InCmd, InAudioDevice, InWorld, [InCmd](USoundWave& InSoundWave)
{
FAudioDebugger::LogSubtitle(InCmd, InSoundWave);
});
};
if (FParse::Param(*Cmd, TEXT("AllViews")))
{
if (FAudioDeviceManager* DeviceManager = FAudioDeviceManager::Get())
{
DeviceManager->IterateOverAllDevices([PlayDebugSoundWave, Cmd, World](Audio::FDeviceId, FAudioDevice* AudioDevice)
{
if (AudioDevice)
{
PlayDebugSoundWave(*Cmd, *AudioDevice, *World);
}
});
}
}
else if (World)
{
if (FAudioDevice* AudioDevice = World->GetAudioDeviceRaw())
{
PlayDebugSoundWave(*Cmd, *AudioDevice, *World);
}
}
}
void HandleStopDebugSound(const TArray<FString>& InArgs, UWorld* InWorld)
{
if (!InWorld)
{
return;
}
FString Cmd = FString::Join(InArgs, TEXT(" "));
if (FParse::Param(*Cmd, TEXT("AllViews")))
{
if (FAudioDeviceManager* DeviceManager = FAudioDeviceManager::Get())
{
DeviceManager->IterateOverAllDevices([InWorld](Audio::FDeviceId, FAudioDevice* AudioDevice)
{
if (AudioDevice)
{
AudioDevice->StopTestComponent();
}
});
}
}
else if (InWorld)
{
if (FAudioDevice* AudioDevice = InWorld->GetAudioDeviceRaw())
{
AudioDevice->StopTestComponent();
}
}
}
void DebugSoundObject(const TArray<FString>& Args, UWorld* InWorld, const FName InStatToEnable, bool& bAllEnabled)
{
if (Args.Num() > 0)
{
bool bAllViews = false;
for (int32 i = 1; i < Args.Num(); ++i)
{
bAllViews |= Args[i] == TEXT("-AllViews");
}
bool bEnablementRequest = Args[0].ToBool();
bAllEnabled = bAllViews ? bEnablementRequest : false;
TSet<FName> Stats;
Stats.Add(InStatToEnable);
bEnablementRequest
? Audio::FAudioDebugger::SetStats(Stats, bAllViews ? nullptr : InWorld)
: Audio::FAudioDebugger::ClearStats(InStatToEnable, bAllViews ? nullptr : InWorld);
}
}
bool DebugShouldRenderStat(UWorld* World, FCanvas* Canvas, bool bEnablementBool, const FName InAudioStat, FAudioDevice** OutAudioDevice)
{
if (!Canvas || !World)
{
return false;
}
*OutAudioDevice = World->GetAudioDeviceRaw();
if (!*OutAudioDevice)
{
return false;
}
if (bEnablementBool)
{
return true;
}
if (!bEnablementBool)
{
FAudioStats* AudioStats = AudioDeviceStats.Find((*OutAudioDevice)->DeviceID);
if (!AudioStats || !(AudioStats->EnabledStats.Contains(InAudioStat)) )
{
return false;
}
}
return true;
}
} // namespace Audio
// Console Commands
static FAutoConsoleCommandWithWorld GAudioDumpActiveSounds(TEXT("au.DumpActiveSounds"), TEXT("Outputs data about all the currently active sounds."), FConsoleCommandWithWorldDelegate::CreateStatic(&Audio::HandleDumpActiveSounds), ECVF_Cheat);
static FAutoConsoleCommandWithWorld GAudioClearMutesAndSolos(TEXT("au.ClearMutesAndSolos"), TEXT("Clears any solo-ing/mute-ing sounds"), FConsoleCommandWithWorldDelegate::CreateStatic(&Audio::HandleClearMutesAndSolos), ECVF_Cheat);
static FAutoConsoleCommandWithWorldAndArgs GAudioPlayDebugSoundCue
(
TEXT("au.Debug.PlaySoundCue"),
TEXT("Plays a SoundCue:\n")
TEXT("-Name <SoundName>: If a debug sound with the short name is specified in AudioSettings, plays that sound.\n")
TEXT("-Path <ObjectPath>: Finds SoundCue asset at the provided path and if found, plays that sound.\n")
TEXT("-Radius <Distance>: If set, enables sound spatialization and sets radial distance between listener and source emitting sound.\n")
TEXT("-Azimuth <Angle>: If set, enables sound spatialization and sets azimuth angle between listener and source emitting sound (in degrees, where 0 is straight ahead, negative to left, positive to right).\n")
TEXT("-Elevation <Angle>: If set, enables sound spatialization and sets azimuth angle between listener and source emitting sound (in degrees, where 0 is straight ahead, negative to left, positive to right).\n")
TEXT("-AllViews: If option provided, plays sound through all viewports.\n")
TEXT("-LogSubtitles: If option provided, logs sounds subtitle if set\n"),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&Audio::HandlePlayDebugSoundCue),
ECVF_Cheat
);
static FAutoConsoleCommandWithWorldAndArgs GAudioPlayDebugSoundWave
(
TEXT("au.Debug.PlaySoundWave"),
TEXT("Plays a SoundWave:\n")
TEXT("-Name <SoundName>: If a debug sound with the short name is specified in AudioSettings, plays that sound.\n")
TEXT("-Path <ObjectPath>: Finds SoundWave asset at the provided path and if found, plays that sound.\n")
TEXT("-Radius: If set, enables sound spatialization and sets radial distance between listener and source emitting sound.\n")
TEXT("-Azimuth <Angle>: If set, enables sound spatialization and sets azimuth angle between listener and source emitting sound (in degrees, where 0 is straight ahead, negative to left, positive to right).\n")
TEXT("-Elevation <Angle>: If set, enables sound spatialization and sets azimuth angle between listener and source emitting sound (in degrees, where 0 is straight ahead, negative to left, positive to right).\n")
TEXT("-AllViews: If option provided, plays sound through all viewports.\n")
TEXT("-LogSubtitles: If option provided, logs sounds subtitle if set\n"),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&Audio::HandlePlayDebugSoundWave),
ECVF_Cheat
);
static FAutoConsoleCommandWithWorldAndArgs GAudioStopDebugSound
(
TEXT("au.Debug.StopSound"),
TEXT("Stops debug sound.\n")
TEXT("-AllViews: If option provided, stops all debug sounds in all viewports.\n"),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&Audio::HandleStopDebugSound),
ECVF_Cheat
);
static FAutoConsoleCommandWithWorldAndArgs GAudioDebugSoundCues
(
TEXT("au.Debug.SoundCues"),
TEXT("Post SoundCue information to viewport(s).\n")
TEXT("0: Disable, 1: Enable\n")
TEXT("(Optional) -AllViews: Enables/Disables for all viewports, not just those associated with the current world"),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic([](const TArray<FString>& Args, UWorld* InWorld)
{
Audio::DebugSoundObject(Args, InWorld, Audio::DebugStatNames::SoundCues, Audio::bDebugCuesForAllViewsEnabled);
}), ECVF_Cheat
);
static FAutoConsoleCommandWithWorldAndArgs GAudioDebugSounds
(
TEXT("au.Debug.Sounds"),
TEXT("Post Sound information to viewport(s).\n")
TEXT("0: Disable, 1: Enable\n")
TEXT("(Optional) -AllViews: Enables/Disables for all viewports, not just those associated with the current world"),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic([](const TArray<FString>& Args, UWorld* InWorld)
{
Audio::DebugSoundObject(Args, InWorld, Audio::DebugStatNames::Sounds, Audio::bDebugSoundsForAllViewsEnabled);
}), ECVF_Cheat
);
static FAutoConsoleCommandWithWorldAndArgs GAudioDebugSoundWaves
(
TEXT("au.Debug.SoundWaves"),
TEXT("Post SoundWave information to viewport(s).\n")
TEXT("0: Disable, 1: Enable\n")
TEXT("(Optional) -AllViews: Enables/Disables for all viewports, not just those associated with the current world"),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic([](const TArray<FString>& Args, UWorld* InWorld)
{
Audio::DebugSoundObject(Args, InWorld, Audio::DebugStatNames::SoundWaves, Audio::bDebugWavesForAllViewsEnabled);
}), ECVF_Cheat
);
static FAutoConsoleCommandWithWorldAndArgs GAudioDebugSoundMixes
(
TEXT("au.Debug.SoundMixes"),
TEXT("Post SoundMix information to viewport(s).\n")
TEXT("0: Disable, 1: Enable\n")
TEXT("(Optional) -AllViews: Enables/Disables for all viewports, not just those associated with the current world"),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic([](const TArray<FString>& Args, UWorld* InWorld)
{
Audio::DebugSoundObject(Args, InWorld, Audio::DebugStatNames::SoundMixes, Audio::bDebugMixesForAllViewsEnabled);
}), ECVF_Cheat
);
static FAutoConsoleCommandWithWorldAndArgs GAudioDebugSoundReverb
(
TEXT("au.Debug.SoundReverb"),
TEXT("Post SoundReverb information to viewport(s).\n")
TEXT("0: Disable, 1: Enable\n")
TEXT("(Optional) -AllViews: Enables/Disables for all viewports, not just those associated with the current world"),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic([](const TArray<FString>& Args, UWorld* InWorld)
{
Audio::DebugSoundObject(Args, InWorld, Audio::DebugStatNames::SoundReverb, Audio::bDebugReverbForAllViewsEnabled);
}), ECVF_Cheat
);
static FAutoConsoleCommandWithWorldAndArgs GAudioDebugSoundModulation
(
TEXT("au.Debug.SoundModulators"),
TEXT("Post SoundModulation information to viewport(s).\n")
TEXT("0: Disable, 1: Enable\n")
TEXT("(Optional) -AllViews: Enables/Disables for all viewports, not just those associated with the current world"),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic([](const TArray<FString>& Args, UWorld* InWorld)
{
Audio::DebugSoundObject(Args, InWorld, Audio::DebugStatNames::SoundModulation, Audio::bDebugModulationForAllViewsEnabled);
}), ECVF_Cheat
);
static FAutoConsoleCommandWithWorldAndArgs GAudioDebugStreaming
(
TEXT("au.Debug.Streaming"),
TEXT("Post Stream Caching information to viewport(s).\n")
TEXT("0: Disable, 1: Enable\n")
TEXT("(Optional) -AllViews: Enables/Disables for all viewports, not just those associated with the current world"),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic([](const TArray<FString>& Args, UWorld* InWorld)
{
Audio::DebugSoundObject(Args, InWorld, Audio::DebugStatNames::AudioStreaming, Audio::bDebugStreamingForAllViewsEnabled);
}), ECVF_Cheat
);
namespace Audio
{
/** Audio Debugger Implementation */
FAudioDebugger::FAudioDebugger()
: bVisualize3dDebug(0)
{
WorldRegisteredWithDeviceHandle = FAudioDeviceWorldDelegates::OnWorldRegisteredToAudioDevice.AddLambda([this](const UWorld* InWorld, FDeviceId InDeviceId)
{
TSet<FName> StatsToSet;
if (bDebugSoundsForAllViewsEnabled)
{
StatsToSet.Add(DebugStatNames::Sounds);
}
if (bDebugCuesForAllViewsEnabled)
{
StatsToSet.Add(DebugStatNames::SoundCues);
}
if (bDebugWavesForAllViewsEnabled)
{
StatsToSet.Add(DebugStatNames::SoundWaves);
}
if (bDebugModulationForAllViewsEnabled)
{
StatsToSet.Add(DebugStatNames::SoundModulation);
}
if (bDebugMixesForAllViewsEnabled)
{
StatsToSet.Add(DebugStatNames::SoundMixes);
}
if (bDebugReverbForAllViewsEnabled)
{
StatsToSet.Add(DebugStatNames::SoundReverb);
}
if (bDebugStreamingForAllViewsEnabled)
{
StatsToSet.Add(DebugStatNames::AudioStreaming);
}
SetStats(InDeviceId, StatsToSet);
});
}
bool FAudioDebugger::IsVisualizeDebug3dEnabled() const
{
return bVisualize3dDebug;
}
void FAudioDebugger::ToggleVisualizeDebug3dEnabled()
{
bVisualize3dDebug = !bVisualize3dDebug;
}
#if WITH_EDITOR
void FAudioDebugger::OnBeginPIE()
{
FAudioDeviceManager* DeviceManager = GEngine->GetAudioDeviceManager();
if (DeviceManager)
{
DeviceManager->GetDebugger().ClearMutesAndSolos();
}
}
void FAudioDebugger::OnEndPIE()
{
FAudioDeviceManager* DeviceManager = GEngine->GetAudioDeviceManager();
if (DeviceManager)
{
DeviceManager->GetDebugger().ClearMutesAndSolos();
}
}
#endif // WITH_EDITOR
void FAudioDebugger::QuerySoloMuteSoundClass(const FString& Name, bool& bOutIsSoloed, bool& bOutIsMuted, FString& OutReason) const
{
GetDebugSoloMuteStateX(Name, DebugNames.SoloSoundClass, DebugNames.MuteSoundClass, bOutIsSoloed, bOutIsMuted, OutReason);
}
void FAudioDebugger::QuerySoloMuteSoundWave(const FString& Name, bool& bOutIsSoloed, bool& bOutIsMuted, FString& OutReason) const
{
GetDebugSoloMuteStateX(Name, DebugNames.SoloSoundWave, DebugNames.MuteSoundWave, bOutIsSoloed, bOutIsMuted, OutReason);
}
void FAudioDebugger::QuerySoloMuteSoundCue(const FString& Name, bool& bOutIsSoloed, bool& bOutIsMuted, FString& OutReason) const
{
GetDebugSoloMuteStateX(Name, DebugNames.SoloSoundCue, DebugNames.MuteSoundCue, bOutIsSoloed, bOutIsMuted, OutReason);
}
void FAudioDebugger::SetNameArray(FName InName, TArray<FName>& InNameArray, bool bOnOff)
{
ExecuteCmdOnAudioThread([InName, &InNameArray, bOnOff]
{
if (bOnOff)
{
InNameArray.AddUnique(InName);
}
else
{
InNameArray.Remove(InName);
}
});
}
void FAudioDebugger::ToggleNameArray(FName InName, TArray<FName>& InNameArray, bool bExclusive )
{
ExecuteCmdOnAudioThread([InName, &InNameArray, bExclusive]
{
// On already?
int32 IndexOf = InNameArray.IndexOfByKey(InName);
if (IndexOf != INDEX_NONE)
{
if (bExclusive)
{
// Turn off everything if we are exclusive.
InNameArray.Empty();
}
else
{
InNameArray.RemoveAtSwap(IndexOf);
}
}
else // Add it.
{
// If we are exclusive, turn off everything else first.
if (bExclusive)
{
InNameArray.Empty();
}
// Allow for NAME_None to act as a clear
if (InName != NAME_None)
{
InNameArray.Add(InName);
}
}
});
}
void FAudioDebugger::SetAudioMixerDebugSound(const TCHAR* SoundName)
{
ExecuteCmdOnAudioThread([Name = FString(SoundName), this]{ DebugNames.DebugAudioMixerSoundName = Name; });
}
void FAudioDebugger::SetAudioDebugSound(const TCHAR* SoundName)
{
ExecuteCmdOnAudioThread([Name = FString(SoundName), this]{
DebugNames.DebugSoundName = Name;
DebugNames.bDebugSoundName = DebugNames.DebugSoundName != TEXT("");
});
}
const FString& FAudioDebugger::GetAudioMixerDebugSoundName() const
{
check(IsInAudioThread());
return DebugNames.DebugAudioMixerSoundName;
}
bool FAudioDebugger::GetAudioDebugSound(FString& OutDebugSound)
{
check(IsInAudioThread());
if (DebugNames.bDebugSoundName)
{
OutDebugSound = DebugNames.DebugSoundName;
return true;
}
return false;
}
void FAudioDebugger::ExecuteCmdOnAudioThread(TFunction<void()> Cmd)
{
// If not on audio thread, queue it.
if (!IsInAudioThread())
{
FAudioThread::RunCommandOnAudioThread(Cmd);
return;
}
// Otherwise, do it inline.
Cmd();
}
void FAudioDebugger::GetDebugSoloMuteStateX(
const FString& Name,
const TArray<FName>& Solos,
const TArray<FName>& Mutes,
bool& bOutIsSoloed,
bool& bOutIsMuted,
FString& OutReason) const
{
check(IsInAudioThread());
// Allow for partial matches of the name.
auto MatchesPartOfName = [&Name](FName i) -> bool { return Name.Contains(i.GetPlainNameString()); };
// Solo active?
if (Solos.Num() > 0)
{
if (Solos.ContainsByPredicate(MatchesPartOfName))
{
bOutIsSoloed = true;
OutReason = FString::Printf(TEXT("Sound is soloed explicitly."));
}
else
{
// Something else is soloed (record the first item in the solo list for debug reason).
bOutIsMuted = true;
OutReason = FString::Printf(TEXT("Sound is muted due to [%s] being soloed"), *Solos[0].ToString() );
}
}
// Are we explicitly muted?
else if (Mutes.ContainsByPredicate(MatchesPartOfName))
{
bOutIsMuted = true;
OutReason = FString::Printf(TEXT("Sound is explicitly muted"));
}
}
void FAudioDebugger::DrawDebugInfo(const FSoundSource& SoundSource)
{
#if ENABLE_DRAW_DEBUG
const FWaveInstance* WaveInstance = SoundSource.GetWaveInstance();
if (!WaveInstance)
{
return;
}
const FActiveSound* ActiveSound = WaveInstance->ActiveSound;
if (!ActiveSound)
{
return;
}
if (!SpatialSourceVisualizeEnabledCVar)
{
return;
}
FAudioDeviceManager* DeviceManager = GEngine->GetAudioDeviceManager();
if (DeviceManager && DeviceManager->IsVisualizeDebug3dEnabled())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.DrawSourceDebugInfo"), STAT_AudioDrawSourceDebugInfo, STATGROUP_TaskGraphTasks);
const FSoundBuffer* Buffer = SoundSource.GetBuffer();
const bool bSpatialized = Buffer && Buffer->NumChannels == 2 && WaveInstance->GetUseSpatialization();
if (bSpatialized)
{
const FRotator Rotator = ActiveSound->Transform.GetRotation().Rotator();
TWeakObjectPtr<UWorld> WorldPtr = WaveInstance->ActiveSound->GetWeakWorld();
FVector LeftChannelSourceLoc;
FVector RightChannelSourceLoc;
SoundSource.GetChannelLocations(LeftChannelSourceLoc, RightChannelSourceLoc);
FAudioThread::RunCommandOnGameThread([LeftChannelSourceLoc, RightChannelSourceLoc, Rotator, WorldPtr]()
{
if (WorldPtr.IsValid())
{
UWorld* World = WorldPtr.Get();
DrawDebugCrosshairs(World, LeftChannelSourceLoc, Rotator, 20.0f, FColor::Red, false, -1.0f, SDPG_Foreground);
DrawDebugCrosshairs(World, RightChannelSourceLoc, Rotator, 20.0f, FColor::Green, false, -1.0f, SDPG_Foreground);
}
}, GET_STATID(STAT_AudioDrawSourceDebugInfo));
}
}
#endif // ENABLE_DRAW_DEBUG
}
void FAudioDebugger::DrawDebugInfo(const FActiveSound& ActiveSound, const TArray<FWaveInstance*>& ThisSoundsWaveInstances, const float DeltaTime)
{
#if ENABLE_DRAW_DEBUG
if (!ActiveSoundVisualizeModeCVar)
{
return;
}
// Only draw spatialized sounds
const USoundBase* Sound = ActiveSound.GetSound();
if (!Sound || !ActiveSound.bAllowSpatialization)
{
return;
}
const float PlaybackTime = ActiveSound.PlaybackTime;
const float PlaybackTimeNonVirtualized = ActiveSound.PlaybackTimeNonVirtualized;
const bool bOccluded = ActiveSound.bIsOccluded;
// Sounds requiring culling can start and immediately stop repeatedly when subscribed
// concurrency is flooded, so don't show the initial frame.
if (FMath::IsNearlyZero(PlaybackTimeNonVirtualized))
{
return;
}
if (ActiveSoundVisualizeTypeCVar > 0)
{
if (ActiveSoundVisualizeTypeCVar == 1 && ActiveSound.GetAudioComponentID() == 0)
{
return;
}
if (ActiveSoundVisualizeTypeCVar == 2 && ActiveSound.GetAudioComponentID() > 0)
{
return;
}
}
FAudioDeviceManager* DeviceManager = GEngine->GetAudioDeviceManager();
if (DeviceManager && DeviceManager->IsVisualizeDebug3dEnabled())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.DrawActiveSoundDebugInfo"), STAT_AudioDrawActiveSoundDebugInfo, STATGROUP_TaskGraphTasks);
const FString Name = Sound->GetName();
const FTransform CurTransform = ActiveSound.Transform;
FColor TextColor = FColor::White;
const float CurMaxDistance = ActiveSound.MaxDistance;
float DisplayValue = 0.0f;
float FilterValue = 0.0f;
if (ActiveSoundVisualizeModeCVar == 1 || ActiveSoundVisualizeModeCVar == 2)
{
for (FWaveInstance* WaveInstance : ThisSoundsWaveInstances)
{
DisplayValue = FMath::Max(DisplayValue, WaveInstance->GetVolumeWithDistanceAndOcclusionAttenuation() * WaveInstance->GetDynamicVolume());
}
}
else if (ActiveSoundVisualizeModeCVar == 3)
{
if (ActiveSound.AudioDevice)
{
DisplayValue = ActiveSound.AudioDevice->GetDistanceToNearestListener(ActiveSound.Transform.GetLocation()) / CurMaxDistance;
}
}
else if (ActiveSoundVisualizeModeCVar == 4)
{
TextColor = ActiveSound.DebugColor;
}
else if (ActiveSoundVisualizeModeCVar == 5)
{
DisplayValue = ActiveSound.CurrentOcclusionVolumeAttenuation.GetValue();
FilterValue = ActiveSound.CurrentOcclusionFilterFrequency.GetValue();
}
TWeakObjectPtr<UWorld> WorldPtr = ActiveSound.GetWeakWorld();
FAudioThread::RunCommandOnGameThread([Name, TextColor, CurTransform, DisplayValue, WorldPtr, CurMaxDistance, PlaybackTime, PlaybackTimeNonVirtualized, bOccluded, FilterValue, DeltaTime]()
{
if (WorldPtr.IsValid())
{
static const float ColorRedHue = 0.0f;
static const float ColorGreenHue = 85.0f;
const FVector Location = CurTransform.GetLocation();
UWorld* DebugWorld = WorldPtr.Get();
DrawDebugSphere(DebugWorld, Location, 10.0f, 8, FColor::White, false, -1.0f, SDPG_Foreground);
FColor Color = TextColor;
FString Descriptor;
if (ActiveSoundVisualizeModeCVar == 1 || ActiveSoundVisualizeModeCVar == 2)
{
const float DisplayDbVolume = Audio::ConvertToDecibels(DisplayValue);
if (ActiveSoundVisualizeModeCVar == 1)
{
Descriptor = FString::Printf(TEXT(" (Vol: %.3f [Active: %.2fs, Playing: %.2fs])"), DisplayValue, PlaybackTime, PlaybackTimeNonVirtualized);
}
else
{
Descriptor = FString::Printf(TEXT(" (Vol: %.3f dB [Active: %.2fs, Playing: %.2fs])"), DisplayDbVolume, PlaybackTime, PlaybackTimeNonVirtualized);
}
static const float DbColorMinVol = -30.0f;
const float DbVolume = FMath::Clamp(DisplayDbVolume, DbColorMinVol, 0.0f);
const float Hue = FMath::Lerp(ColorRedHue, ColorGreenHue, (-1.0f * DbVolume / DbColorMinVol) + 1.0f);
Color = FLinearColor::MakeFromHSV8(static_cast<uint8>(Hue), 255u, 255u).ToFColor(true);
}
else if (ActiveSoundVisualizeModeCVar == 3)
{
Descriptor = FString::Printf(TEXT(" (Dist: %.3f, Max: %.3f)"), DisplayValue * CurMaxDistance, CurMaxDistance);
const float Hue = FMath::Lerp(ColorGreenHue, ColorRedHue, DisplayValue);
Color = FLinearColor::MakeFromHSV8(static_cast<uint8>(FMath::Clamp(Hue, 0.0f, 255.f)), 255u, 255u).ToFColor(true);
}
else if (ActiveSoundVisualizeModeCVar == 5)
{
Descriptor = FString::Printf(TEXT(" (Occlusion Volume: %.3f, Occlusion Filter: %.3f)"), DisplayValue, FilterValue);
if (bOccluded)
{
Color = FColor::Red;
}
else
{
Color = FColor::Green;
}
}
const FString Description = FString::Printf(TEXT("%s%s"), *Name, *Descriptor);
DrawDebugString(DebugWorld, Location + FVector(0, 0, 32), *Description, nullptr, Color, DeltaTime, false);
}
}, GET_STATID(STAT_AudioDrawActiveSoundDebugInfo));
}
#endif // ENABLE_DRAW_DEBUG
}
void FAudioDebugger::DrawDebugInfo(UWorld& World, const TArray<FListener>& Listeners)
{
#if ENABLE_DRAW_DEBUG
if (!ActiveSoundVisualizeListenersCVar)
{
return;
}
check(IsInAudioThread());
FAudioDeviceManager* DeviceManager = GEngine->GetAudioDeviceManager();
if (DeviceManager && DeviceManager->IsVisualizeDebug3dEnabled())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.DrawListenerDebugInfo"), STAT_AudioDrawListenerDebugInfo, STATGROUP_TaskGraphTasks);
const TWeakObjectPtr<UWorld> WorldPtr = &World;
for (const FListener& Listener : Listeners)
{
const FVector ListenerPosition = Listener.GetPosition(true);
const FVector ListenerFront = Listener.GetFront();
const FVector ListenerUp = Listener.GetUp();
const FVector ListenerRight = Listener.GetRight();
FAudioThread::RunCommandOnGameThread([WorldPtr, ListenerPosition, ListenerFront, ListenerUp, ListenerRight]()
{
if (WorldPtr.IsValid())
{
static float ArrowLength = 30.0f;
static float ArrowHeadSize = 8.0f;
static float Lifetime = -1.0f;
static float Thickness = 0.9f;
static uint8 DepthPriority = SDPG_Foreground;
static bool bPersistentLines = false;
DrawDebugDirectionalArrow(WorldPtr.Get(), ListenerPosition, ListenerPosition + (ArrowLength * ListenerFront), ArrowHeadSize, FColor::Red, bPersistentLines, Lifetime, DepthPriority, Thickness);
DrawDebugDirectionalArrow(WorldPtr.Get(), ListenerPosition, ListenerPosition + (ArrowLength * ListenerUp), ArrowHeadSize, FColor::Blue, bPersistentLines, Lifetime, DepthPriority, Thickness);
DrawDebugDirectionalArrow(WorldPtr.Get(), ListenerPosition, ListenerPosition + (ArrowLength * ListenerRight), ArrowHeadSize, FColor::Green, bPersistentLines, Lifetime, DepthPriority, Thickness);
DrawDebugSphere(WorldPtr.Get(), ListenerPosition, 5.0f, 8, FColor::Magenta, bPersistentLines, Lifetime, DepthPriority);
}
}, GET_STATID(STAT_AudioDrawListenerDebugInfo));
}
}
#endif // ENABLE_DRAW_DEBUG
}
void FAudioDebugger::DrawDebugInfo(const FAudioVirtualLoop& VirtualLoop)
{
#if ENABLE_DRAW_DEBUG
if (!GEngine)
{
return;
}
if (!VirtualLoopsVisualizeEnabledCVar)
{
return;
}
FAudioDeviceManager* DeviceManager = GEngine->GetAudioDeviceManager();
if (DeviceManager && DeviceManager->IsVisualizeDebug3dEnabled())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.DrawVirtualLoopDebugInfo"), STAT_AudioDrawVirtualLoopDebugInfo, STATGROUP_TaskGraphTasks);
const FActiveSound& ActiveSound = VirtualLoop.GetActiveSound();
USoundBase* Sound = ActiveSound.GetSound();
check(Sound);
const FTransform Transform = ActiveSound.Transform;
const TWeakObjectPtr<UWorld> World = ActiveSound.GetWeakWorld();
const FString Name = Sound->GetName();
const float DrawInterval = VirtualLoop.GetUpdateInterval();
const float TimeVirtualized = VirtualLoop.GetTimeVirtualized();
FAudioThread::RunCommandOnGameThread([World, Transform, Name, DrawInterval, TimeVirtualized]()
{
if (World.IsValid())
{
const FString Description = FString::Printf(TEXT("%s [Virt: %.2fs]"), *Name, TimeVirtualized);
FVector Location = Transform.GetLocation();
FRotator Rotation = Transform.GetRotation().Rotator();
DrawDebugCrosshairs(World.Get(), Location, Rotation, 20.0f, FColor::Blue, false, DrawInterval, SDPG_Foreground);
DrawDebugString(World.Get(), Location + FVector(0, 0, 32), *Description, nullptr, FColor::Blue, DrawInterval, false);
}
}, GET_STATID(STAT_AudioDrawVirtualLoopDebugInfo));
}
#endif // ENABLE_DRAW_DEBUG
}
int32 FAudioDebugger::DrawDebugStats(UWorld& World, FViewport* OutViewport, FCanvas* InCanvas, int32 InY)
{
check(IsInGameThread());
if (OutViewport)
{
return DrawDebugStatsInternal(World, *OutViewport, InCanvas, InY);
}
if (UGameViewportClient* GameViewport = World.GetGameViewport())
{
if (GameViewport->Viewport)
{
return DrawDebugStatsInternal(World, *GameViewport->Viewport, InCanvas, InY);
}
return InY;
}
#if WITH_EDITOR
if (GEditor)
{
// Level editor viewport may not be a game viewport, but should still display debug stats, so check if this case is covered and display accordingly
const TArray<FLevelEditorViewportClient*>& LevelViewportClients = GEditor->GetLevelViewportClients();
for (FLevelEditorViewportClient* ViewportClient : LevelViewportClients)
{
if (ViewportClient && ViewportClient->Viewport)
{
UWorld* EditorWorld = ViewportClient->GetWorld();
if (EditorWorld && EditorWorld == &World)
{
const FText SystemDisplayName = NSLOCTEXT("AudioDebugger", "AudioDebugger_DrawDebugData", "Audio Debug Data");
if (DrawDebugStatsEnabled())
{
ViewportClient->AddRealtimeOverride(true, SystemDisplayName);
}
ViewportClient->RemoveRealtimeOverride(SystemDisplayName);
return DrawDebugStatsInternal(World, *ViewportClient->Viewport, InCanvas, InY);
}
}
}
}
#endif // WITH_EDITOR
return InY;
}
bool FAudioDebugger::DrawDebugStatsEnabled()
{
return bDebugWavesForAllViewsEnabled
|| bDebugCuesForAllViewsEnabled
|| bDebugSoundsForAllViewsEnabled
|| bDebugMixesForAllViewsEnabled
|| bDebugReverbForAllViewsEnabled
|| bDebugModulationForAllViewsEnabled
|| bDebugStreamingForAllViewsEnabled;
}
int32 FAudioDebugger::DrawDebugStatsInternal(UWorld& World, FViewport& Viewport, FCanvas* InCanvas, int32 InY)
{
FCanvas* Canvas = InCanvas ? InCanvas : Viewport.GetDebugCanvas();
if (!Canvas)
{
return InY;
}
int32 X = SoundDebugDisplayCornerXCVar;
int32 Y = SoundDebugDisplayCornerYCVar < 0 ? InY : SoundDebugDisplayCornerYCVar;
Y = RenderStatMixes(&World, &Viewport, Canvas, X, Y);
Y = RenderStatModulators(&World, &Viewport, Canvas, X, Y, nullptr, nullptr);
Y = RenderStatReverb(&World, &Viewport, Canvas, X, Y);
Y = RenderStatSounds(&World, &Viewport, Canvas, X, Y);
Y = RenderStatCues(&World, &Viewport, Canvas, X, Y);
Y = RenderStatWaves(&World, &Viewport, Canvas, X, Y);
Y = RenderStatStreaming(&World, &Viewport, Canvas, X, Y, nullptr, nullptr);
return Y;
}
void FAudioDebugger::DumpActiveSounds() const
{
if (!GEngine)
{
return;
}
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.DumpActiveSounds"), STAT_AudioDumpActiveSounds, STATGROUP_TaskGraphTasks);
FAudioThread::RunCommandOnAudioThread([this]()
{
DumpActiveSounds();
}, GET_STATID(STAT_AudioDumpActiveSounds));
return;
}
FAudioDeviceHandle AudioDevice = GEngine->GetAudioDeviceManager()->GetActiveAudioDevice();
if (!AudioDevice)
{
return;
}
const TArray<FActiveSound*>& ActiveSounds = AudioDevice->GetActiveSounds();
UE_LOG(LogAudio, Display, TEXT("Active Sound Count: %d"), ActiveSounds.Num());
UE_LOG(LogAudio, Display, TEXT("------------------------"), ActiveSounds.Num());
for (const FActiveSound* ActiveSound : ActiveSounds)
{
if (ActiveSound)
{
UE_LOG(LogAudio, Display, TEXT("%s (%.3g) - %s"), *ActiveSound->GetSound()->GetName(), ActiveSound->GetSound()->GetDuration(), *ActiveSound->GetAudioComponentName());
for (const TPair<UPTRINT, FWaveInstance*>& WaveInstancePair : ActiveSound->GetWaveInstances())
{
const FWaveInstance* WaveInstance = WaveInstancePair.Value;
UE_LOG(LogAudio, Display, TEXT(" %s (%.3g) (%d) - %.3g"),
*WaveInstance->GetName(), WaveInstance->WaveData->GetDuration(),
WaveInstance->WaveData->GetResourceSizeBytes(EResourceSizeMode::EstimatedTotal),
WaveInstance->GetVolumeWithDistanceAndOcclusionAttenuation() * WaveInstance->GetDynamicVolume());
}
}
}
}
void FAudioDebugger::ResolveDesiredStats(FViewportClient* ViewportClient)
{
if (!ViewportClient)
{
return;
}
FAudioDevice* AudioDevice = GetWorldAudio(ViewportClient->GetWorld());
if (!AudioDevice)
{
return;
}
TSet<FName> SetStatFlags;
if (ViewportClient->IsStatEnabled(TEXT("SoundCues")))
{
SetStatFlags.Add(DebugStatNames::SoundCues);
}
if (ViewportClient->IsStatEnabled(TEXT("SoundWaves")))
{
SetStatFlags.Add(DebugStatNames::SoundWaves);
}
if (ViewportClient->IsStatEnabled(TEXT("SoundMixes")))
{
SetStatFlags.Add(DebugStatNames::SoundMixes);
}
if (ViewportClient->IsStatEnabled(TEXT("Sounds")))
{
FAudioStats& Stats = AudioDeviceStats.FindOrAdd(AudioDevice->DeviceID);
SetStatFlags.Add(DebugStatNames::Sounds);
if (Stats.DisplayFlags & static_cast<uint8>(FAudioStats::EDisplayFlags::Debug))
{
SetStatFlags.Add(DebugStatNames::DebugSounds);
}
if (Stats.DisplayFlags & static_cast<uint8>(FAudioStats::EDisplayFlags::Long_Names))
{
SetStatFlags.Add(DebugStatNames::LongSoundNames);
}
}
if (ViewportClient->IsStatEnabled(TEXT("Streaming")))
{
SetStatFlags.Add(DebugStatNames::AudioStreaming);
}
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.ResolveDesiredStats"), STAT_AudioResolveDesiredStats, STATGROUP_TaskGraphTasks);
const uint32 DeviceID = AudioDevice->DeviceID;
if (IsInAudioThread())
{
FAudioStats_AudioThread& Stats = AudioDeviceStats_AudioThread.FindOrAdd(DeviceID);
Stats.RequestedStats = SetStatFlags;
}
else
{
FAudioThread::RunCommandOnAudioThread([SetStatFlags, DeviceID]()
{
FAudioStats_AudioThread& Stats = AudioDeviceStats_AudioThread.FindOrAdd(DeviceID);
Stats.RequestedStats = SetStatFlags;
}, GET_STATID(STAT_AudioResolveDesiredStats));
}
}
int32 FAudioDebugger::RenderStatCues(UWorld* World, FViewport* Viewport, FCanvas* Canvas, int32 X, int32 Y)
{
FAudioDevice* AudioDevice = nullptr;
if (!DebugShouldRenderStat(World, Canvas, bDebugCuesForAllViewsEnabled, DebugStatNames::SoundCues, &AudioDevice))
{
return Y;
}
UFont* StatsFont = GetStatsFont();
const int32 FontSpacing = 2;
const int32 FontHeight = StatsFont->GetMaxCharHeight() + FontSpacing;
Canvas->DrawShadowedString(X, Y, TEXT("Active Sound Cues:"), StatsFont, HeaderColor);
Y += FontHeight;
int32 ActiveSoundCount = 0;
FAudioStats& AudioStats = AudioDeviceStats.FindOrAdd(AudioDevice->DeviceID);
if (!SoundCueDebugMinimalCVar)
{
for (const FAudioStats::FStatSoundInfo& StatSoundInfo : AudioStats.StatSoundInfos)
{
for (const FAudioStats::FStatWaveInstanceInfo& WaveInstanceInfo : StatSoundInfo.WaveInstanceInfos)
{
if (WaveInstanceInfo.Volume >= MinDisplayVolume)
{
FColor Color = FColor::White;
FString MuteSoloReason;
if (FSoundSource::FDebugInfo* DebugInfo = WaveInstanceInfo.DebugInfo.Get())
{
// Color code same as icons. Red (mute), Yellow (solo), White (normal).
FScopeLock Lock(&DebugInfo->CS);
Color = DebugInfo->bIsMuted ? FColor::Red : DebugInfo->bIsSoloed ? FColor::Yellow : FColor::White;
MuteSoloReason = !DebugInfo->MuteSoloReason.IsEmpty() ? FString::Printf(TEXT(" - %s"), *DebugInfo->MuteSoloReason) : TEXT("");
}
const FString TheString = FString::Printf(TEXT("%4i. %6.2f %s %s %s"), ActiveSoundCount++, WaveInstanceInfo.Volume, *StatSoundInfo.SoundName, *StatSoundInfo.SoundClassName.ToString(), *MuteSoloReason);
Canvas->DrawShadowedString(X, Y, *TheString, StatsFont, Color);
Y += FontHeight;
break;
}
}
}
}
else
{
FString SoundPath, SoundName;
static int32 PrevNameLength = 0;
static int32 PrevClassLength = 0;
const int32 TabSpacing = FMath::Clamp(SoundCueDebugTabSpacingCVar, 1, SoundCueDebugTabSpacingCVar);
const int32 CharSpacing = FMath::Clamp(SoundCueDebugCharSpacingCVar, 1, SoundCueDebugCharSpacingCVar);
const int32 NumberSpacing = 6 * CharSpacing; // 6 character len for 2 decimal float + 2 spaces 'X.XX '
// Tab out name and class length and reset previous length counters
int32 TabbedName = (PrevNameLength / TabSpacing + 1) * TabSpacing;
int32 TabbedClass = (PrevClassLength / TabSpacing + 1) * TabSpacing;
PrevNameLength = PrevClassLength = 0;
for (const FAudioStats::FStatSoundInfo& StatSoundInfo : AudioStats.StatSoundInfos)
{
for (const FAudioStats::FStatWaveInstanceInfo& WaveInstanceInfo : StatSoundInfo.WaveInstanceInfos)
{
if (WaveInstanceInfo.Volume >= MinDisplayVolume)
{
FColor Color = FColor::White;
FString MuteSoloReason;
bool bMutedOrSoloed = false;
if (!StatSoundInfo.SoundPath.Split(TEXT("."), &SoundPath, &SoundName))
{
if (!StatSoundInfo.SoundPath.Split(SUBOBJECT_DELIMITER, &SoundPath, &SoundName))
{
SoundPath = StatSoundInfo.SoundPath;
}
}
SoundName = StatSoundInfo.SoundName;
if (SoundName.Len() > PrevNameLength)
{
PrevNameLength = SoundName.Len();
}
if ((int32)StatSoundInfo.SoundClassName.GetStringLength() > PrevClassLength)
{
PrevClassLength = StatSoundInfo.SoundClassName.GetStringLength();
}
if (FSoundSource::FDebugInfo* DebugInfo = WaveInstanceInfo.DebugInfo.Get())
{
// Color code same as icons. Red (mute), Yellow (solo), White (normal).
FScopeLock Lock(&DebugInfo->CS);
Color = DebugInfo->bIsMuted ? FColor::Red : DebugInfo->bIsSoloed ? FColor::Yellow : FColor::White;
bMutedOrSoloed = DebugInfo->bIsMuted || DebugInfo->bIsSoloed;
MuteSoloReason = !DebugInfo->MuteSoloReason.IsEmpty() ? FString::Printf(TEXT(" - %s"), *DebugInfo->MuteSoloReason) : TEXT("");
}
const int32 SoundNameIndex = ActiveSoundCount++;
const FString LeadingNumber = FString::Printf(TEXT("%4i. "), SoundNameIndex);
const FString Volume = FString::Printf(TEXT("%6.2f "), WaveInstanceInfo.Volume);
const FString PathAndMuting = FString::Printf(TEXT("Path: %s %s"), *SoundPath, *MuteSoloReason);
int32 CurrentX = X;
Canvas->DrawShadowedString(CurrentX, Y, *LeadingNumber, StatsFont, Color);
CurrentX += NumberSpacing;
Canvas->DrawShadowedString(CurrentX, Y, *Volume, StatsFont, bMutedOrSoloed ? Color : FColor::Orange);
CurrentX += NumberSpacing;
if (SoundCueDebugShowDistanceCVar)
{
const FString DistanceText = FString::Printf(TEXT("%6.2f "), StatSoundInfo.Distance);
Canvas->DrawShadowedString(CurrentX, Y, *DistanceText, StatsFont, bMutedOrSoloed ? Color : FColor::White);
CurrentX += (NumberSpacing * 2);
}
Canvas->DrawShadowedString(CurrentX, Y, *SoundName, StatsFont, bMutedOrSoloed ? Color : FColor(0, 255, 255));
CurrentX += (TabbedName * CharSpacing);
Canvas->DrawShadowedString(CurrentX, Y, *StatSoundInfo.SoundClassName.ToString(), StatsFont, bMutedOrSoloed ? Color : FColor::Yellow);
if (SoundCueDebugShowPathCVar)
{
CurrentX += (TabbedClass * CharSpacing);
Canvas->DrawShadowedString(CurrentX, Y, *PathAndMuting, StatsFont, Color);
}
Y += FontHeight;
break;
}
}
}
}
Canvas->DrawShadowedString(X, Y, *FString::Printf(TEXT("Total: %i"), ActiveSoundCount), GetStatsFont(), GetBodyColor());
Y += FontHeight;
return Y;
}
int32 FAudioDebugger::RenderStatMixes(UWorld* World, FViewport* Viewport, FCanvas* Canvas, int32 X, int32 Y)
{
FAudioDevice* AudioDevice = nullptr;
if (!DebugShouldRenderStat(World, Canvas, bDebugMixesForAllViewsEnabled, DebugStatNames::SoundMixes, &AudioDevice))
{
return Y;
}
const int32 FontHeight = GetStatsFont()->GetMaxCharHeight() + 2;
FAudioStats& AudioStats = AudioDeviceStats.FindOrAdd(AudioDevice->DeviceID);
Canvas->DrawShadowedString(X, Y, TEXT("Active Sound Mixes:"), GetStatsFont(), HeaderColor);
Y += FontHeight;
bool bDisplayedSoundMixes = false;
if (AudioStats.StatSoundMixes.Num() > 0)
{
bDisplayedSoundMixes = true;
for (const FAudioStats::FStatSoundMix& StatSoundMix : AudioStats.StatSoundMixes)
{
const FString TheString = FString::Printf(TEXT("%s - Fade Proportion: %1.2f - Total Ref Count: %i"), *StatSoundMix.MixName, StatSoundMix.InterpValue, StatSoundMix.RefCount);
const FColor& TextColor = (StatSoundMix.bIsCurrentEQ ? FColor::Yellow : GetBodyColor());
Canvas->DrawShadowedString(X + DebuggerTabWidth, Y, *TheString, GetStatsFont(), TextColor);
Y += FontHeight;
}
}
if (!bDisplayedSoundMixes)
{
Canvas->DrawShadowedString(X + DebuggerTabWidth, Y, TEXT("None"), GetStatsFont(), GetBodyColor());
Y += FontHeight;
}
Y += FontHeight;
return Y;
}
int32 FAudioDebugger::RenderStatModulators(UWorld* World, FViewport* Viewport, FCanvas* Canvas, int32 X, int32 Y, const FVector* ViewLocation, const FRotator* ViewRotation)
{
FAudioDevice* AudioDevice = nullptr;
if (!DebugShouldRenderStat(World, Canvas, bDebugModulationForAllViewsEnabled, DebugStatNames::SoundModulation, &AudioDevice))
{
return Y;
}
const int32 FontHeight = GetStatsFont()->GetMaxCharHeight() + 2;
Canvas->DrawShadowedString(X, Y, TEXT("Active Sound Modulation:"), GetStatsFont(), HeaderColor);
Y += FontHeight;
bool bDisplayedSoundModulationInfo = false;
if (IAudioModulationManager* Modulation = AudioDevice->ModulationInterface.Get())
{
const int32 YInit = Y;
Y = Modulation->OnRenderStat(Viewport, Canvas, X, Y, *GetStatsFont(), ViewLocation, ViewRotation);
bDisplayedSoundModulationInfo = Y != YInit;
}
if (!bDisplayedSoundModulationInfo)
{
Canvas->DrawShadowedString(X + DebuggerTabWidth, Y, TEXT("None"), GetStatsFont(), GetBodyColor());
Y += FontHeight;
}
Y += FontHeight;
return Y;
}
int32 FAudioDebugger::RenderStatReverb(UWorld* World, FViewport* Viewport, FCanvas* Canvas, int32 X, int32 Y)
{
FAudioDevice* AudioDevice = nullptr;
if (!DebugShouldRenderStat(World, Canvas, bDebugReverbForAllViewsEnabled, DebugStatNames::SoundReverb, &AudioDevice))
{
return Y;
}
const int32 Height = static_cast<int32>(GetStatsFont()->GetMaxCharHeight() + 2);
FString TheString;
const FLinearColor LinearBodyColor = FLinearColor(GetBodyColor());
if (UReverbEffect* ReverbEffect = AudioDevice->GetCurrentReverbEffect())
{
TheString = FString::Printf(TEXT("Active Reverb Effect: %s"), *ReverbEffect->GetName());
Canvas->DrawShadowedString(X, Y, *TheString, GetStatsFont(), HeaderColor);
Y += Height;
AAudioVolume* CurrentAudioVolume = nullptr;
const int32 ProxyCount = AudioDevice->ListenerProxies.Num();
for (int i = 0; i < ProxyCount; ++i)
{
const FTransform& Transform = AudioDevice->ListenerProxies[i].Transform;
AAudioVolume* PlayerAudioVolume = World->GetAudioSettings(Transform.GetLocation(), nullptr, nullptr);
if (PlayerAudioVolume && ((CurrentAudioVolume == nullptr) || (PlayerAudioVolume->GetPriority() > CurrentAudioVolume->GetPriority())))
{
CurrentAudioVolume = PlayerAudioVolume;
}
}
if (CurrentAudioVolume && CurrentAudioVolume->GetReverbSettings().ReverbEffect)
{
TheString = FString::Printf(TEXT(" Audio Volume Reverb Effect: %s (Priority: %g Volume Name: %s)"), *CurrentAudioVolume->GetReverbSettings().ReverbEffect->GetName(), CurrentAudioVolume->GetPriority(), *CurrentAudioVolume->GetName());
}
else
{
TheString = TEXT(" Audio Volume Reverb Effect: None");
}
Canvas->DrawShadowedString(X, Y, *TheString, GetStatsFont(), LinearBodyColor);
Y += Height;
const TMap<FName, FActivatedReverb>& ActivatedReverbs = AudioDevice->GetActiveReverb();
if (ActivatedReverbs.Num() == 0)
{
TheString = TEXT(" Activated Reverb: None");
Canvas->DrawShadowedString(X, Y, *TheString, GetStatsFont(), LinearBodyColor);
Y += Height;
}
else if (ActivatedReverbs.Num() == 1)
{
auto It = ActivatedReverbs.CreateConstIterator();
TheString = FString::Printf(TEXT(" Activated Reverb Effect: %s (Priority: %g Tag: '%s')"), *It.Value().ReverbSettings.ReverbEffect->GetName(), It.Value().Priority, *It.Key().ToString());
Canvas->DrawShadowedString(X, Y, *TheString, GetStatsFont(), LinearBodyColor);
Y += Height;
}
else
{
Canvas->DrawShadowedString(X, Y, TEXT(" Activated Reverb Effects:"), GetStatsFont(), LinearBodyColor);
Y += Height;
TMap<int32, FString> PrioritySortedActivatedReverbs;
for (auto It = ActivatedReverbs.CreateConstIterator(); It; ++It)
{
TheString = FString::Printf(TEXT(" %s (Priority: %g Tag: '%s')"), *It.Value().ReverbSettings.ReverbEffect->GetName(), It.Value().Priority, *It.Key().ToString());
PrioritySortedActivatedReverbs.Add(It.Value().Priority, TheString);
}
for (auto It = PrioritySortedActivatedReverbs.CreateConstIterator(); It; ++It)
{
Canvas->DrawShadowedString(X, Y, *It.Value(), GetStatsFont(), LinearBodyColor);
Y += Height;
}
}
}
else
{
TheString = TEXT("Active Reverb Effect: None");
Canvas->DrawShadowedString(X, Y, *TheString, GetStatsFont(), HeaderColor);
Y += Height;
}
Y += Height;
return Y;
}
int32 FAudioDebugger::RenderStatSounds(UWorld* World, FViewport* Viewport, FCanvas* Canvas, int32 X, int32 Y)
{
FAudioDevice* AudioDevice = nullptr;
if (!DebugShouldRenderStat(World, Canvas, bDebugSoundsForAllViewsEnabled, DebugStatNames::Sounds, &AudioDevice))
{
return Y;
}
const int32 FontHeight = GetStatsFont()->GetMaxCharHeight() + 2;
FAudioStats& AudioStats = AudioDeviceStats.FindOrAdd(AudioDevice->DeviceID);
const uint8 bDebug = AudioStats.DisplayFlags & static_cast<uint8>(FAudioStats::EDisplayFlags::Debug);
UpdateDisplaySort(AudioStats);
// Sort the list.
FString SortingName;
switch (AudioStats.DisplaySort)
{
case FAudioStats::EDisplaySort::Class:
{
AudioStats.StatSoundInfos.Sort([](const FAudioStats::FStatSoundInfo& A, const FAudioStats::FStatSoundInfo& B) { return A.SoundClassName.LexicalLess(B.SoundClassName); });
SortingName = TEXT("Class");
break;
}
case FAudioStats::EDisplaySort::Distance:
{
AudioStats.StatSoundInfos.Sort([](const FAudioStats::FStatSoundInfo& A, const FAudioStats::FStatSoundInfo& B) { return A.Distance < B.Distance; });
SortingName = TEXT("Distance");
break;
}
case FAudioStats::EDisplaySort::PlaybackTime:
{
AudioStats.StatSoundInfos.Sort([](const FAudioStats::FStatSoundInfo& A, const FAudioStats::FStatSoundInfo& B) { return A.PlaybackTime > B.PlaybackTime; });
SortingName = TEXT("Time");
break;
}
case FAudioStats::EDisplaySort::Priority:
{
AudioStats.StatSoundInfos.Sort([](const FAudioStats::FStatSoundInfo& A, const FAudioStats::FStatSoundInfo& B) { return A.Priority > B.Priority; });
SortingName = TEXT("Priority");
break;
}
case FAudioStats::EDisplaySort::Waves:
{
AudioStats.StatSoundInfos.Sort([](const FAudioStats::FStatSoundInfo& A, const FAudioStats::FStatSoundInfo& B) { return A.WaveInstanceInfos.Num() > B.WaveInstanceInfos.Num(); });
SortingName = TEXT("Waves");
break;
}
case FAudioStats::EDisplaySort::Volume:
{
AudioStats.StatSoundInfos.Sort([](const FAudioStats::FStatSoundInfo& A, const FAudioStats::FStatSoundInfo& B) { return A.Volume > B.Volume; });
SortingName = TEXT("Volume");
break;
}
case FAudioStats::EDisplaySort::Name:
default:
{
AudioStats.StatSoundInfos.Sort([](const FAudioStats::FStatSoundInfo& A, const FAudioStats::FStatSoundInfo& B)
{
if (AudioDebugSoundShowPathCVar)
{
return A.SoundPath < B.SoundPath;
}
return A.SoundName < B.SoundName;
});
SortingName = TEXT("Name");
break;
}
}
Canvas->DrawShadowedString(X, Y, TEXT("Active Sounds:"), GetStatsFont(), HeaderColor);
Y += FontHeight;
const FString InfoText = FString::Printf(TEXT(" Sorting By: %s, Visualize Attenuation: %s"), *SortingName, bDebug ? TEXT("Enabled") : TEXT("Disabled"));
Canvas->DrawShadowedString(X, Y, *InfoText, GetStatsFont(), FColor(128, 255, 128));
Y += FontHeight;
static int32 FieldLength = 40;
static int32 NameHeaderPad = static_cast<int32>(FieldLength * 1.25f);
if (AudioStats.DisplaySort == FAudioStats::EDisplaySort::Name || AudioStats.DisplaySort == FAudioStats::EDisplaySort::Distance)
{
const FString FieldName = FString(TEXT("Distance")).RightPad(NameHeaderPad).Left(NameHeaderPad);
Canvas->DrawShadowedString(X, Y, *FString::Printf(TEXT("Index %s Name"), *FieldName), GetStatsFont(), GetBodyColor());
}
else
{
const FString FieldName = SortingName.RightPad(FieldLength).Left(FieldLength);
Canvas->DrawShadowedString(X, Y, *FString::Printf(TEXT("Index %s Name"), *FieldName), GetStatsFont(), GetBodyColor());
}
Y += FontHeight;
int32 TotalSoundWavesNum = 0;
const FColor BodyColor = GetStatSoundBodyColor();
for (int32 SoundIndex = 0; SoundIndex < AudioStats.StatSoundInfos.Num(); ++SoundIndex)
{
const FAudioStats::FStatSoundInfo& StatSoundInfo = AudioStats.StatSoundInfos[SoundIndex];
const int32 WaveInstancesNum = StatSoundInfo.WaveInstanceInfos.Num();
if (WaveInstancesNum == 0)
{
continue;
}
TotalSoundWavesNum += WaveInstancesNum;
if (SoundIndex >= AudioDebugSoundMaxNumDisplayedCVar)
{
continue;
}
bool bDisplayWaves = false;
const FString DisplayName = AudioDebugSoundShowPathCVar ? StatSoundInfo.SoundPath : StatSoundInfo.SoundName;
FString DebugValue;
switch (AudioStats.DisplaySort)
{
case FAudioStats::EDisplaySort::Class:
{
DebugValue = *StatSoundInfo.SoundClassName.ToString();
break;
}
case FAudioStats::EDisplaySort::PlaybackTime:
{
if (FMath::IsNearlyEqual(StatSoundInfo.PlaybackTime, StatSoundInfo.PlaybackTimeNonVirtualized))
{
DebugValue = FString::Printf(TEXT("%08.2f"), StatSoundInfo.PlaybackTime);
}
else
{
DebugValue = FString::Printf(TEXT("%08.2f (%08.2f Non-Virt)"), StatSoundInfo.PlaybackTime, StatSoundInfo.PlaybackTimeNonVirtualized);
}
break;
}
case FAudioStats::EDisplaySort::Priority:
{
if (FMath::IsNearlyEqual(StatSoundInfo.Priority, TNumericLimits<float>::Max()))
{
DebugValue += TEXT("Always ");
}
else
{
DebugValue = FString::Printf(TEXT("%06.2f"), StatSoundInfo.Priority);
}
break;
}
case FAudioStats::EDisplaySort::Volume:
{
DebugValue = FString::Printf(TEXT("%01.2f (%04.2f dB)"), StatSoundInfo.Volume, Audio::ConvertToDecibels(StatSoundInfo.Volume));
break;
}
case FAudioStats::EDisplaySort::Waves:
{
DebugValue = FString::Printf(TEXT("%03u"), StatSoundInfo.WaveInstanceInfos.Num());
bDisplayWaves = true;
break;
}
case FAudioStats::EDisplaySort::Name:
case FAudioStats::EDisplaySort::Distance:
default:
{
DebugValue = FString::Printf(TEXT("%08.2f"), StatSoundInfo.Distance);
break;
}
}
const FString DebugStr = FString::Printf(TEXT(" %03i %s %s"), SoundIndex, *DebugValue.RightPad(FieldLength - 4).Left(FieldLength - 4), *DisplayName);
Canvas->DrawShadowedString(X, Y, *DebugStr, GetStatsFont(), BodyColor);
Y += FontHeight;
if (bDisplayWaves)
{
for (int32 WaveIndex = 0; WaveIndex < WaveInstancesNum; WaveIndex++)
{
const FString WaveStr = *FString::Printf(TEXT(" %02i %s"), WaveIndex, *StatSoundInfo.WaveInstanceInfos[WaveIndex].Description);
Canvas->DrawShadowedString(X, Y, *WaveStr, GetStatsFont(), FColor(205, 205, 205));
Y += FontHeight;
}
}
}
Canvas->DrawShadowedString(X, Y, *FString::Printf(TEXT("Audio Device ID: %u, Max Sounds Displayed: %i"), AudioDevice->DeviceID, AudioDebugSoundMaxNumDisplayedCVar), GetStatsFont(), HeaderColor);
Y += FontHeight;
Canvas->DrawShadowedString(X, Y, *FString::Printf(TEXT("Total Sounds: %i, Sound Waves: %i"), AudioStats.StatSoundInfos.Num(), TotalSoundWavesNum), GetStatsFont(), HeaderColor);
Y += FontHeight;
for (int32 i = 0; i < AudioStats.ListenerTransforms.Num(); ++i)
{
FString LocStr = AudioStats.ListenerTransforms[i].GetLocation().ToString();
Canvas->DrawShadowedString(X, Y, *FString::Printf(TEXT("Listener '%d' Position: %s"), i, *LocStr), GetStatsFont(), HeaderColor);
Y += FontHeight;
}
Y += FontHeight;
if (!bDebug)
{
return Y;
}
// Draw sound cue's sphere only in debug.
for (const FAudioStats::FStatSoundInfo& StatSoundInfo : AudioStats.StatSoundInfos)
{
const FTransform& SoundTransform = StatSoundInfo.Transform;
const int32 WaveInstancesNum = StatSoundInfo.WaveInstanceInfos.Num();
if (StatSoundInfo.Distance > 100.0f && WaveInstancesNum > 0)
{
float SphereRadius = 0.f;
float SphereInnerRadius = 0.f;
if (StatSoundInfo.ShapeDetailsMap.Num() > 0)
{
const FString DebugName = AudioDebugSoundShowPathCVar ? StatSoundInfo.SoundPath : StatSoundInfo.SoundName;
DrawDebugString(World, SoundTransform.GetTranslation(), *DebugName, nullptr, BodyColor, 0.01f);
for (auto ShapeDetailsIt = StatSoundInfo.ShapeDetailsMap.CreateConstIterator(); ShapeDetailsIt; ++ShapeDetailsIt)
{
const FBaseAttenuationSettings::AttenuationShapeDetails& ShapeDetails = ShapeDetailsIt.Value();
switch (ShapeDetailsIt.Key())
{
case EAttenuationShape::Sphere:
if (ShapeDetails.Falloff > 0.f)
{
DrawDebugSphere(World, SoundTransform.GetTranslation(), ShapeDetails.Extents.X + ShapeDetails.Falloff, 10, FColor(155, 155, 255));
DrawDebugSphere(World, SoundTransform.GetTranslation(), ShapeDetails.Extents.X, 10, FColor(55, 55, 255));
}
else
{
DrawDebugSphere(World, SoundTransform.GetTranslation(), ShapeDetails.Extents.X, 10, FColor(155, 155, 255));
}
break;
case EAttenuationShape::Box:
if (ShapeDetails.Falloff > 0.f)
{
DrawDebugBox(World, SoundTransform.GetTranslation(), ShapeDetails.Extents + FVector(ShapeDetails.Falloff), SoundTransform.GetRotation(), FColor(155, 155, 255));
DrawDebugBox(World, SoundTransform.GetTranslation(), ShapeDetails.Extents, SoundTransform.GetRotation(), FColor(55, 55, 255));
}
else
{
DrawDebugBox(World, SoundTransform.GetTranslation(), ShapeDetails.Extents, SoundTransform.GetRotation(), FColor(155, 155, 255));
}
break;
case EAttenuationShape::Capsule:
if (ShapeDetails.Falloff > 0.f)
{
DrawDebugCapsule(World, SoundTransform.GetTranslation(), ShapeDetails.Extents.X + ShapeDetails.Falloff, ShapeDetails.Extents.Y + ShapeDetails.Falloff, SoundTransform.GetRotation(), FColor(155, 155, 255));
DrawDebugCapsule(World, SoundTransform.GetTranslation(), ShapeDetails.Extents.X, ShapeDetails.Extents.Y, SoundTransform.GetRotation(), FColor(55, 55, 255));
}
else
{
DrawDebugCapsule(World, SoundTransform.GetTranslation(), ShapeDetails.Extents.X, ShapeDetails.Extents.Y, SoundTransform.GetRotation(), FColor(155, 155, 255));
}
break;
case EAttenuationShape::Cone:
{
const FVector Origin = SoundTransform.GetTranslation() - (SoundTransform.GetUnitAxis(EAxis::X) * ShapeDetails.ConeOffset);
if (ShapeDetails.Falloff > 0.f || ShapeDetails.Extents.Z > 0.f)
{
const float OuterAngle = FMath::DegreesToRadians(ShapeDetails.Extents.Y + ShapeDetails.Extents.Z);
const float InnerAngle = FMath::DegreesToRadians(ShapeDetails.Extents.Y);
DrawDebugCone(World, Origin, SoundTransform.GetUnitAxis(EAxis::X), ShapeDetails.Extents.X + ShapeDetails.Falloff + ShapeDetails.ConeOffset, OuterAngle, OuterAngle, 10, FColor(155, 155, 255));
DrawDebugCone(World, Origin, SoundTransform.GetUnitAxis(EAxis::X), ShapeDetails.Extents.X + ShapeDetails.ConeOffset, InnerAngle, InnerAngle, 10, FColor(55, 55, 255));
}
else
{
const float Angle = FMath::DegreesToRadians(ShapeDetails.Extents.Y);
DrawDebugCone(World, Origin, SoundTransform.GetUnitAxis(EAxis::X), ShapeDetails.Extents.X + ShapeDetails.ConeOffset, Angle, Angle, 10, FColor(155, 155, 255));
}
if (!FMath::IsNearlyZero(ShapeDetails.ConeSphereRadius, UE_KINDA_SMALL_NUMBER))
{
if (ShapeDetails.ConeSphereFalloff > 0.f)
{
DrawDebugSphere(World, Origin, ShapeDetails.ConeSphereRadius + ShapeDetails.ConeSphereFalloff, 10, FColor(155, 155, 255));
DrawDebugSphere(World, Origin, ShapeDetails.ConeSphereRadius, 10, FColor(55, 55, 255));
}
else
{
DrawDebugSphere(World, Origin, ShapeDetails.ConeSphereRadius, 10, FColor(155, 155, 255));
}
}
break;
}
default:
check(false);
}
}
}
}
}
return Y;
}
int32 FAudioDebugger::RenderStatWaves(UWorld* World, FViewport* Viewport, FCanvas* Canvas, int32 X, int32 Y)
{
FAudioDevice* AudioDevice = nullptr;
if (!DebugShouldRenderStat(World, Canvas, bDebugWavesForAllViewsEnabled, DebugStatNames::SoundWaves, &AudioDevice))
{
return Y;
}
const int32 FontHeight = GetStatsFont()->GetMaxCharHeight() + 2;
FAudioStats& AudioStats = AudioDeviceStats.FindOrAdd(AudioDevice->DeviceID);
Canvas->DrawShadowedString(X, Y, TEXT("Active Sound Waves:"), GetStatsFont(), FLinearColor::Green);
Y += DebuggerTabWidth;
using FWaveInstancePair = TPair<const FAudioStats::FStatWaveInstanceInfo*, const FAudioStats::FStatSoundInfo*>;
TArray<FWaveInstancePair> WaveInstances;
for (const FAudioStats::FStatSoundInfo& StatSoundInfo : AudioStats.StatSoundInfos)
{
for (const FAudioStats::FStatWaveInstanceInfo& WaveInstanceInfo : StatSoundInfo.WaveInstanceInfos)
{
if (WaveInstanceInfo.Volume >= MinDisplayVolume || WaveInstanceInfo.bPlayWhenSilent != 0)
{
WaveInstances.Emplace(&WaveInstanceInfo, &StatSoundInfo);
}
}
}
WaveInstances.Sort([](const FWaveInstancePair& A, const FWaveInstancePair& B) { return A.Key->InstanceIndex < B.Key->InstanceIndex; });
const FColor BodyColor = GetBodyColor();
for (const FWaveInstancePair& WaveInstanceInfo : WaveInstances)
{
UAudioComponent* AudioComponent = UAudioComponent::GetAudioComponentFromID(WaveInstanceInfo.Value->AudioComponentID);
AActor* SoundOwner = AudioComponent ? AudioComponent->GetOwner() : nullptr;
FString TheString = *FString::Printf(TEXT("%4i. %6.2f %s Owner: %s SoundClass: %s"),
WaveInstanceInfo.Key->InstanceIndex,
WaveInstanceInfo.Key->Volume,
*WaveInstanceInfo.Key->WaveInstanceName.ToString(),
SoundOwner ? *SoundOwner->GetName() : TEXT("None"),
*WaveInstanceInfo.Key->SoundClassName.ToString());
Canvas->DrawShadowedString(X, Y, *TheString, GetStatsFont(), WaveInstanceInfo.Key->bPlayWhenSilent == 0 ? BodyColor : FColor::Yellow);
Y += FontHeight;
}
const int32 ActiveInstances = WaveInstances.Num();
const int32 Max = AudioDevice->GetMaxChannels() / 2;
float f = FMath::Clamp<float>((float)(ActiveInstances - Max) / (float)Max, 0.f, 1.f);
const int32 R = FMath::TruncToInt(f * 255);
if (ActiveInstances > Max)
{
f = FMath::Clamp<float>((float)(Max - ActiveInstances) / (float)Max, 0.5f, 1.f);
}
else
{
f = 1.0f;
}
const int32 G = FMath::TruncToInt(f * 255);
const int32 B = 0;
Canvas->DrawShadowedString(X, Y, *FString::Printf(TEXT(" Total: %i"), ActiveInstances), GetStatsFont(), FColor(R, G, B));
Y += 2 * FontHeight;
return Y;
}
int32 FAudioDebugger::RenderStatStreaming(UWorld* World, FViewport* Viewport, FCanvas* Canvas, int32 X, int32 Y, const FVector* ViewLocation /*= nullptr*/, const FRotator* ViewRotation /*= nullptr*/)
{
FAudioDevice* AudioDevice = nullptr;
if (!DebugShouldRenderStat(World, Canvas, bDebugSoundsForAllViewsEnabled, DebugStatNames::AudioStreaming, &AudioDevice))
{
return Y;
}
return IStreamingManager::Get().GetAudioStreamingManager().RenderStatAudioStreaming(World, Viewport, Canvas, X, Y, ViewLocation, ViewRotation);
}
void FAudioDebugger::RemoveDevice(const FAudioDevice& AudioDevice)
{
AudioDeviceStats.Remove(AudioDevice.DeviceID);
AudioDeviceStats_AudioThread.Remove(AudioDevice.DeviceID);
}
void FAudioDebugger::ClearStats(const FName StatsToClear, UWorld* InWorld)
{
if (!GEngine)
{
return;
}
if (FAudioDeviceManager* DeviceManager = GEngine->GetAudioDeviceManager())
{
Audio::FAudioDebugger& DebuggerInstance = DeviceManager->GetDebugger();
DeviceManager->IterateOverAllDevices(
[&DebuggerInstance, World = InWorld, ClearedStats = StatsToClear](FDeviceId DeviceId, const FAudioDevice* AudioDevice)
{
if (!World || World->GetAudioDevice().GetDeviceID() == DeviceId)
{
DebuggerInstance.ClearStats(DeviceId, ClearedStats);
}
}
);
}
}
void FAudioDebugger::ClearStats(FDeviceId DeviceId, const FName StatToClear)
{
if (IsInGameThread())
{
FAudioStats& Stats = AudioDeviceStats.FindOrAdd(DeviceId);
Stats.EnabledStats.Remove(StatToClear);
}
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.ClearStats"), STAT_AudioClearStats, STATGROUP_TaskGraphTasks);
FAudioThread::RunCommandOnAudioThread([this, DeviceId, StatToClear]()
{
ClearStats(DeviceId, StatToClear);
}, GET_STATID(STAT_AudioClearStats));
return;
}
FAudioStats_AudioThread& Stats = AudioDeviceStats_AudioThread.FindOrAdd(DeviceId);
Stats.RequestedStats.Remove(StatToClear);
}
void FAudioDebugger::SetStats(const TSet<FName> & StatsToSet, UWorld* InWorld)
{
if (!GEngine)
{
return;
}
if (FAudioDeviceManager* DeviceManager = GEngine->GetAudioDeviceManager())
{
Audio::FAudioDebugger& DebuggerInstance = DeviceManager->GetDebugger();
DeviceManager->IterateOverAllDevices(
[&DebuggerInstance, World = InWorld, StatsSet = StatsToSet](FDeviceId DeviceId, const FAudioDevice* AudioDevice)
{
if (!World || World->GetAudioDevice().GetDeviceID() == DeviceId)
{
DebuggerInstance.SetStats(DeviceId, StatsSet);
}
}
);
}
}
void FAudioDebugger::SetStats(FDeviceId DeviceId, const TSet<FName> & StatsToSet)
{
if (IsInGameThread())
{
FAudioStats& Stats = AudioDeviceStats.FindOrAdd(DeviceId);
Stats.EnabledStats.Append(StatsToSet);
}
if (!IsInAudioThread())
{
DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.SetStats"), STAT_AudioSetStats, STATGROUP_TaskGraphTasks);
FAudioThread::RunCommandOnAudioThread([this, DeviceId, StatsToSet]()
{
SetStats(DeviceId, StatsToSet);
}, GET_STATID(STAT_AudioSetStats));
return;
}
FAudioStats_AudioThread& Stats = AudioDeviceStats_AudioThread.FindOrAdd(DeviceId);
Stats.RequestedStats.Append(StatsToSet);
}
bool FAudioDebugger::PostStatModulatorHelp(UWorld* World, FCommonViewportClient* ViewportClient, const TCHAR* Stream)
{
// Ignore if all Viewports are closed.
if (!ViewportClient)
{
return false;
}
if (FAudioDevice* AudioDevice = World->GetAudioDeviceRaw())
{
if (AudioDevice->IsModulationPluginEnabled())
{
if (IAudioModulationManager* Modulation = AudioDevice->ModulationInterface.Get())
{
if (!Modulation->OnPostHelp(ViewportClient, Stream))
{
return false;
}
}
}
}
return true;
}
void FAudioDebugger::SendUpdateResultsToGameThread(const FAudioDevice& AudioDevice, const int32 FirstActiveIndex)
{
check(IsInAudioThread());
FAudioStats_AudioThread* Stats_AudioThread = AudioDeviceStats_AudioThread.Find(AudioDevice.DeviceID);
if (!Stats_AudioThread)
{
return;
}
TArray<FAudioStats::FStatSoundInfo> StatSoundInfos;
TArray<FAudioStats::FStatSoundMix> StatSoundMixes;
const TSet<FName> RequestedStats = Stats_AudioThread->RequestedStats;
TMap<FActiveSound*, int32> ActiveSoundToInfoIndex;
const bool bDebug = RequestedStats.Contains(DebugStatNames::DebugSounds);
if (RequestedStats.Contains(DebugStatNames::Sounds) || RequestedStats.Contains(DebugStatNames::SoundCues)
|| RequestedStats.Contains(DebugStatNames::SoundMixes) || RequestedStats.Contains(DebugStatNames::SoundWaves))
{
for (FActiveSound* ActiveSound : AudioDevice.GetActiveSounds())
{
if (USoundBase* SoundBase = ActiveSound->GetSound())
{
if (!bDebug || ActiveSound->GetSound()->bDebug)
{
ActiveSoundToInfoIndex.Add(ActiveSound, StatSoundInfos.AddDefaulted());
FAudioStats::FStatSoundInfo& StatSoundInfo = StatSoundInfos.Last();
StatSoundInfo.SoundName = SoundBase->GetName();
StatSoundInfo.SoundPath = SoundBase->GetPathName();
StatSoundInfo.Distance = 0.f;
if (ActiveSound->bAllowSpatialization)
{
StatSoundInfo.Distance = AudioDevice.GetDistanceToNearestListener(ActiveSound->Transform.GetTranslation());
}
StatSoundInfo.PlaybackTime = ActiveSound->PlaybackTime;
StatSoundInfo.Priority = ActiveSound->GetHighestPriority();
StatSoundInfo.PlaybackTimeNonVirtualized = ActiveSound->PlaybackTimeNonVirtualized;
StatSoundInfo.Volume = 0.0f;
for (const TPair<UPTRINT, FWaveInstance*>& Pair : ActiveSound->GetWaveInstances())
{
StatSoundInfo.Volume = FMath::Max(StatSoundInfo.Volume, Pair.Value->GetVolumeWithDistanceAndOcclusionAttenuation() * Pair.Value->GetDynamicVolume());
}
if (USoundClass* SoundClass = ActiveSound->GetSoundClass())
{
StatSoundInfo.SoundClassName = SoundClass->GetFName();
}
else
{
StatSoundInfo.SoundClassName = NAME_None;
}
StatSoundInfo.Transform = ActiveSound->Transform;
StatSoundInfo.AudioComponentID = ActiveSound->GetAudioComponentID();
if (bDebug && ActiveSound->GetSound()->bDebug)
{
ActiveSound->CollectAttenuationShapesForVisualization(StatSoundInfo.ShapeDetailsMap);
}
}
}
}
// Iterate through all wave instances.
const TArray<FWaveInstance*>& WaveInstances = AudioDevice.GetActiveWaveInstances();
auto WaveInstanceSourceMap = AudioDevice.GetWaveInstanceSourceMap();
for (int32 InstanceIndex = FirstActiveIndex; InstanceIndex < WaveInstances.Num(); ++InstanceIndex)
{
const FWaveInstance* WaveInstance = WaveInstances[InstanceIndex];
const FActiveSound* ActiveSound = WaveInstance->ActiveSound;
check(ActiveSound);
if (const int32* SoundInfoIndex = ActiveSoundToInfoIndex.Find(ActiveSound))
{
const USoundClass* SoundClass = ActiveSound->GetSoundClass();
if (const USoundClass* WaveSoundClass = WaveInstance->SoundClass) //-V1051
{
SoundClass = WaveSoundClass;
}
FAudioStats::FStatWaveInstanceInfo WaveInstanceInfo;
FSoundSource* Source = WaveInstanceSourceMap.FindRef(WaveInstance);
WaveInstanceInfo.Description = Source ? Source->Describe(RequestedStats.Contains(DebugStatNames::LongSoundNames)) : FString(TEXT("No source"));
WaveInstanceInfo.Volume = WaveInstance->GetVolumeWithDistanceAndOcclusionAttenuation() * WaveInstance->GetDynamicVolume();
WaveInstanceInfo.InstanceIndex = InstanceIndex;
WaveInstanceInfo.WaveInstanceName = *WaveInstance->GetName();
WaveInstanceInfo.bPlayWhenSilent = ActiveSound->IsPlayWhenSilent() ? 1 : 0;
WaveInstanceInfo.DebugInfo = Source ? Source->DebugInfo : WaveInstanceInfo.DebugInfo;
WaveInstanceInfo.SoundClassName = SoundClass ? SoundClass->GetFName() : NAME_None;
StatSoundInfos[*SoundInfoIndex].WaveInstanceInfos.Add(MoveTemp(WaveInstanceInfo));
}
}
}
if (RequestedStats.Contains(DebugStatNames::SoundMixes))
{
if (const FAudioEffectsManager* Effects = AudioDevice.GetEffects())
{
const USoundMix* CurrentEQMix = Effects->GetCurrentEQMix();
for (const TPair<USoundMix*, FSoundMixState>& SoundMixPair : AudioDevice.GetSoundMixModifiers())
{
StatSoundMixes.AddDefaulted();
FAudioStats::FStatSoundMix& StatSoundMix = StatSoundMixes.Last();
StatSoundMix.MixName = SoundMixPair.Key->GetName();
StatSoundMix.InterpValue = SoundMixPair.Value.InterpValue;
StatSoundMix.RefCount = SoundMixPair.Value.ActiveRefCount + SoundMixPair.Value.PassiveRefCount;
StatSoundMix.bIsCurrentEQ = (SoundMixPair.Key == CurrentEQMix);
}
}
}
DECLARE_CYCLE_STAT(TEXT("FGameThreadAudioTask.AudioSendResults"), STAT_AudioSendResults, STATGROUP_TaskGraphTasks);
const uint32 AudioDeviceID = AudioDevice.DeviceID;
TArray<FTransform> ListenerTransforms;
for (const FListener& Listener : AudioDevice.GetListeners())
{
ListenerTransforms.Add(Listener.Transform);
}
FAudioThread::RunCommandOnGameThread([AudioDeviceID, ListenerTransforms, StatSoundInfos, StatSoundMixes]()
{
FAudioStats& Stats = AudioDeviceStats.FindOrAdd(AudioDeviceID);
Stats.ListenerTransforms = ListenerTransforms;
Stats.StatSoundInfos = StatSoundInfos;
Stats.StatSoundMixes = StatSoundMixes;
}, GET_STATID(STAT_AudioSendResults));
}
void FAudioDebugger::UpdateAudibleInactiveSounds(const uint32 FirstActiveIndex, const TArray<FWaveInstance*>& WaveInstances)
{
#if STATS
uint32 AudibleInactiveSounds = 0;
// Count how many sounds are not being played but were audible
for (uint32 InstanceIndex = 0; InstanceIndex < FirstActiveIndex; ++InstanceIndex)
{
const FWaveInstance* WaveInstance = WaveInstances[InstanceIndex];
const float WaveInstanceVol = WaveInstance->GetVolumeWithDistanceAndOcclusionAttenuation() * WaveInstance->GetDynamicVolume();
if (WaveInstanceVol > MinDisplayVolume)
{
AudibleInactiveSounds++;
}
}
SET_DWORD_STAT(STAT_AudibleWavesDroppedDueToPriority, AudibleInactiveSounds);
#endif
}
void FAudioDebugger::ClearMutesAndSolos()
{
DebugNames.MuteSoundClass.Empty();
DebugNames.MuteSoundCue.Empty();
DebugNames.MuteSoundWave.Empty();
DebugNames.SoloSoundClass.Empty();
DebugNames.SoloSoundCue.Empty();
DebugNames.SoloSoundWave.Empty();
}
void FAudioDebugger::LogSubtitle(const TCHAR* InCmd, USoundWave& InSoundWave)
{
const bool bLogSubtitle = FParse::Param(InCmd, TEXT("LogSubtitle"));
if (bLogSubtitle)
{
FString Subtitle;
for (int32 i = 0; i < InSoundWave.Subtitles.Num(); i++)
{
Subtitle += InSoundWave.Subtitles[i].Text.ToString();
}
if (Subtitle.Len() == 0)
{
Subtitle = InSoundWave.SpokenText_DEPRECATED;
}
if (Subtitle.Len() == 0)
{
Subtitle = "<NO SUBTITLE>";
}
UE_LOG(LogAudio, Display, TEXT("Subtitle: %s"), *Subtitle);
#if WITH_EDITORONLY_DATA
UE_LOG(LogAudio, Display, TEXT("Comment: %s"), *InSoundWave.Comment);
#endif // WITH_EDITORONLY_DATA
UE_LOG(LogAudio, Display, TEXT("Mature: %s"), InSoundWave.bMature ? TEXT("Yes") : TEXT("No"));
}
}
} // namespace Audio
#endif // ENABLE_AUDIO_DEBUG