// Copyright Epic Games, Inc. All Rights Reserved. #include "Commandlets/AudioMixerCommandlet.h" #include "HAL/FileManager.h" #include "Misc/Paths.h" #include "Engine/Engine.h" #include "Engine/EngineBaseTypes.h" #include "Sound/SoundAttenuation.h" #include "Sound/SoundWave.h" #include "Audio.h" #include "UObject/UObjectHash.h" #include "UObject/Package.h" #include "ActiveSound.h" #define ENABLE_AUDIO_MIXER_COMMANDLET (PLATFORM_WINDOWS) #if ENABLE_AUDIO_MIXER_COMMANDLET #include "AudioDevice.h" #include "AudioMixerModule.h" #include "Components/AudioComponent.h" #include "Sound/AudioSettings.h" DEFINE_LOG_CATEGORY_STATIC(AudioMixerCommandlet, Log, All); /************************************************************************ * UTILITY FUNCTIONS AND CLASSES ************************************************************************/ /** Class which circularly rotates around a an offset with a given angular velocity. */ class FPositionRotator { public: FPositionRotator(float InRadius, float InCurrentAngle, float InAngularVelocity, const FVector& InOffset = FVector::ZeroVector) : Radius(InRadius) , CurrentAngle(InCurrentAngle) , AngularVelocity(InAngularVelocity) , Position(FVector::ZeroVector) , Offset(InOffset) { float PosX = Radius * FMath::Cos(CurrentAngle); float PosZ = Radius * FMath::Sin(CurrentAngle); Position = Offset + FVector(PosX, 0.0f, PosZ); } FVector GetPosition() const { return Position; } void Update() { CurrentAngle += AngularVelocity; float PosX = Radius * FMath::Cos(CurrentAngle); float PosZ = Radius * FMath::Sin(CurrentAngle); Position = Offset + FVector(PosX, 0.0f, PosZ); } void SetAngularVelocity(float InAngularVelocity) { AngularVelocity = InAngularVelocity; } float Radius; float CurrentAngle; float AngularVelocity; FVector Position; FVector Offset; }; static USoundWave* LoadSoundWave(const FString& SoundWavePath) { // Load the package UPackage* SoundWaveAssetPkg = LoadPackage(nullptr, *SoundWavePath, LOAD_None); // Get all the objects associated with this package TArray Objects; GetObjectsWithOuter(SoundWaveAssetPkg, Objects); USoundWave* Sound = nullptr; for (UObject* Obj : Objects) { Sound = Cast(Obj); if (Sound != nullptr) { break; } } if (!Sound) { UE_LOG(AudioMixerCommandlet, Error, TEXT("Failed to find a USoundWave for asset path %s"), *SoundWavePath); } return Sound; } static void CreateDefaultSoundSearchPaths(TArray& OutSearchPaths) { OutSearchPaths.Add(FPaths::EngineContentDir() / TEXT("EditorSounds")); OutSearchPaths.Add(FPaths::EngineContentDir() / TEXT("EngineSounds")); } template static void LoadEditorAndEngineObjects(TArray& SearchPaths, TArray& OutObjects, TArray* IgnoreList = nullptr) { for (FString& SearchPath : SearchPaths) { IFileManager& FileManager = IFileManager::Get(); TArray EngineContentFiles; FileManager.FindFilesRecursive(EngineContentFiles, *SearchPath, TEXT("*.uasset"), true, false); for (FString& AssetPath : EngineContentFiles) { UPackage* Package = LoadPackage(nullptr, *AssetPath, LOAD_None); TArray Objects; GetObjectsWithOuter(Package, Objects); for (UObject* Obj : Objects) { T* OutObj = Cast(Obj); if (OutObj != nullptr) { bool bIgnored = false; if (IgnoreList) { for (int i = 0; i < IgnoreList->Num(); ++i) { if (Obj->GetName() == (*IgnoreList)[i]) { bIgnored = true; break; } } } if (!bIgnored) { OutObjects.Add(OutObj); } } } } } UE_LOG(AudioMixerCommandlet, Log, TEXT("Loaded %d objects from engine content directory"), OutObjects.Num()); } static void PlayOneShotSound(FAudioDevice* InAudioDevice, const TArray& InSounds) { // Randomly pick a sound wave USoundWave* SoundWave = nullptr; while (true) { int32 SoundIndex = FMath::RandRange(0, InSounds.Num() - 1); SoundWave = InSounds[SoundIndex]; if (!SoundWave->bLooping) { break; } } // Create an active sound FActiveSound NewActiveSound; NewActiveSound.SetSound(SoundWave); NewActiveSound.SetVolume(0.25f); NewActiveSound.SetPitch(FMath::FRandRange(0.1f, 3.0f)); NewActiveSound.RequestedStartTime = 0.0f; NewActiveSound.bIsUISound = true; NewActiveSound.bAllowSpatialization = false; NewActiveSound.Priority = 1.0f; // Add it to the audio device InAudioDevice->AddNewActiveSound(NewActiveSound); } static UAudioComponent* SpawnLoopingSound(FAudioDevice* InAudioDevice, UWorld* World, const TArray& InSounds, bool bAllowSpatialization = false, USoundAttenuation* SoundAttenuation = nullptr, float StartTime = 0.0f) { int32 SoundIndex = FMath::RandRange(0, InSounds.Num() - 1); USoundWave* SoundWave = InSounds[SoundIndex]; // Set the sound wave to looping SoundWave->bLooping = true; FAudioDevice::FCreateComponentParams Params(World); Params.AttenuationSettings = SoundAttenuation; UAudioComponent* AudioComponent = FAudioDevice::CreateComponent(SoundWave, Params); if (AudioComponent) { AudioComponent->SetVolumeMultiplier(0.5f); AudioComponent->SetPitchMultiplier(1.0f); AudioComponent->bAllowSpatialization = bAllowSpatialization; AudioComponent->bIsUISound = !bAllowSpatialization; AudioComponent->bAutoDestroy = true; } return AudioComponent; } /************************************************************************ * FAudioMixerCommand ************************************************************************/ class FAudioMixerCommand { public: FAudioMixerCommand(const FString& InName, const FString& InDescription = FString(), int32 InNumArgs = 0, const FString& InArgDescription = FString()) : Name(InName) , Description(InDescription) , ArgDescription(InArgDescription) , NumArgs(InNumArgs) { Commands.Add(this); } virtual ~FAudioMixerCommand() { } // Return the name of the test const FString& GetName() const { return Name; } // Return the description of the test const FString& GetDescription() const { return Description; } // Return the num args of the test int32 GetNumArgs() const { return NumArgs; } // Return the description of the test const FString& GetArgDescription() const { return ArgDescription; } // Run the command virtual bool Run(UWorld* World, const TArray& InArgs) = 0; static TArray& GetCommands() { return Commands; } protected: FString Name; FString Description; FString ArgDescription; int32 NumArgs; static TArray Commands; }; TArray FAudioMixerCommand::Commands; /************************************************************************ * FRunAudioDevice ************************************************************************/ class FRunAudioDevice final : public FAudioMixerCommand { public: FRunAudioDevice() : FAudioMixerCommand(TEXT("RunAudioDevice"), TEXT("Create and run an FAudioDevice object."), 1, TEXT("Number of seconds to run.")) {} bool Run(UWorld* World, const TArray& InArgs) override { // Check if we've been told to run the audio device for a certain amount of time float TimeToRunSec = 10.0f; if (InArgs.Num() > 0) { TimeToRunSec = FCString::Atof(*InArgs[0]); } // Get the audio main device FAudioDeviceHandle AudioDevice = GEngine->GetMainAudioDevice(); bool bSuccess = false; if (AudioDevice) { // Get the quality settings of the audio device (uses game user settings) FAudioQualitySettings QualitySettings = AudioDevice->GetQualityLevelSettings(); // Initialize the audio device bSuccess = AudioDevice->Init(AudioDevice.GetDeviceID(), QualitySettings.MaxChannels); if (bSuccess) { // Toggle the audio debug output (sine-wave tones) AudioDevice->EnableDebugAudioOutput(); double CurrentTime = AudioDevice->GetAudioTime(); double StartTime = CurrentTime; while (true) { CurrentTime = AudioDevice->GetAudioTime(); UE_LOG(AudioMixerCommandlet, Log, TEXT("Current Time: %.2f"), CurrentTime); if (CurrentTime - StartTime >= TimeToRunSec) { break; } FPlatformProcess::Sleep(1); } // Teardown the audio device AudioDevice->Teardown(); } } return bSuccess; } }; FRunAudioDevice RunAudioDevice; /************************************************************************ * FPlaySoundWave2D ************************************************************************/ class FPlaySoundWave2D final : public FAudioMixerCommand { public: FPlaySoundWave2D() : FAudioMixerCommand("PlaySoundWave2D", "Load and play a 2D engine test sound wave") {} bool Run(UWorld* World, const TArray& InArgs) override { TArray SearchPaths; CreateDefaultSoundSearchPaths(SearchPaths); TArray SoundWaves; TArray IgnoreList; IgnoreList.Add(TEXT("WhiteNoise")); LoadEditorAndEngineObjects(SearchPaths, SoundWaves, &IgnoreList); FAudioDeviceHandle AudioDevice = GEngine->GetMainAudioDevice(); bool bSuccess = false; float TimeCount = 0.0f; const float DeltaTime = 0.033f; if (AudioDevice) { // Get the quality settings of the audio device (uses game user settings) FAudioQualitySettings QualitySettings = AudioDevice->GetQualityLevelSettings(); // Initialize the audio device bSuccess = AudioDevice->Init(AudioDevice.GetDeviceID(), QualitySettings.MaxChannels); // Wait a few seconds to give the editor a chance to load everything... you get hitches in the beginning otherwise FPlatformProcess::Sleep(1); int32 SoundCount = 0; if (bSuccess) { double CurrentTime = AudioDevice->GetAudioTime(); while (true) { if (TimeCount == 0.0f) { int32 NumActiveSounds = AudioDevice->GetNumActiveSources(); PlayOneShotSound(AudioDevice.GetAudioDevice(), SoundWaves); } CurrentTime = AudioDevice->GetAudioTime(); // Update the audio device AudioDevice->Update(true); // Sleep 33 ms FPlatformProcess::Sleep(DeltaTime); TimeCount += DeltaTime; if (TimeCount > 0.25f) { TimeCount = 0.0f; } } // Teardown the audio device AudioDevice->Teardown(); } } return bSuccess; } }; FPlaySoundWave2D PlaySoundWave2D; /************************************************************************ * FPlaySoundWaveLooping2D ************************************************************************/ class FPlaySoundWaveLooping2D final : public FAudioMixerCommand { public: FPlaySoundWaveLooping2D() : FAudioMixerCommand("PlaySoundWaveLooping2D", "Load and play a single looping 2D engine test sound wave") {} bool Run(UWorld* World, const TArray& InArgs) override { // Load a single large seamless loop added to test path FString LoopingSoundPath = FPaths::EngineContentDir() / TEXT("EngineSounds") / TEXT("TestSounds") / TEXT("Loops"); TArray SearchPaths; SearchPaths.Add(LoopingSoundPath); TArray SoundWaves; TArray IgnoreList; IgnoreList.Add(TEXT("WhiteNoise")); LoadEditorAndEngineObjects(SearchPaths, SoundWaves, &IgnoreList); FAudioDeviceHandle AudioDevice = GEngine->GetMainAudioDevice(); bool bSuccess = false; float TimeCount = 0.0f; const float DeltaTime = 0.033f; if (AudioDevice) { // Get the quality settings of the audio device (uses game user settings) FAudioQualitySettings QualitySettings = AudioDevice->GetQualityLevelSettings(); // Initialize the audio device bSuccess = AudioDevice->Init(AudioDevice.GetDeviceID(), QualitySettings.MaxChannels); // Wait a few seconds to give the editor a chance to load everything... you get hitches in the beginning otherwise FPlatformProcess::Sleep(1); int32 SoundCount = 0; if (bSuccess) { UAudioComponent* LoopingSound = SpawnLoopingSound(AudioDevice.GetAudioDevice(), World, SoundWaves); while (true) { // Update the audio device AudioDevice->Update(true); // Sleep 33 ms FPlatformProcess::Sleep(DeltaTime); TimeCount += DeltaTime; if (TimeCount > 0.25f) { TimeCount = 0.0f; } } // Teardown the audio device AudioDevice->Teardown(); } } return bSuccess; } }; FPlaySoundWaveLooping2D PlaySoundWaveLooping2D; /************************************************************************ * FPlayRealTimeSoundWaveLooping2D ************************************************************************/ class FPlayRealTimeSoundWaveLooping2D final : public FAudioMixerCommand { public: FPlayRealTimeSoundWaveLooping2D() : FAudioMixerCommand("PlayRealTimeSoundWaveLooping2D", "Load and play a single looping 2D engine test sound wave using real-time decoding.") {} bool Run(UWorld* World, const TArray& InArgs) override { // Load a single large seamless loop added to test path FString LoopingSoundPath = FPaths::EngineContentDir() / TEXT("EngineSounds") / TEXT("TestSounds") / TEXT("Loops"); TArray SearchPaths; SearchPaths.Add(LoopingSoundPath); TArray SoundWaves; TArray IgnoreList; IgnoreList.Add(TEXT("WhiteNoise")); LoadEditorAndEngineObjects(SearchPaths, SoundWaves, &IgnoreList); // Set the looping sound waves sound groups to one that has a 0-second threshold for (USoundWave* SoundWave : SoundWaves) { SoundWave->SoundGroup = ESoundGroup::SOUNDGROUP_Music; } FAudioDeviceHandle AudioDevice = GEngine->GetMainAudioDevice(); bool bSuccess = false; float TimeCount = 0.0f; const float DeltaTime = 0.033f; if (AudioDevice) { // Get the quality settings of the audio device (uses game user settings) FAudioQualitySettings QualitySettings = AudioDevice->GetQualityLevelSettings(); // Initialize the audio device bSuccess = AudioDevice->Init(AudioDevice.GetDeviceID(), QualitySettings.MaxChannels); // Wait a few seconds to give the editor a chance to load everything... you get hitches in the beginning otherwise FPlatformProcess::Sleep(1); int32 SoundCount = 0; if (bSuccess) { UAudioComponent* LoopingSound = SpawnLoopingSound(AudioDevice.GetAudioDevice(), World, SoundWaves); LoopingSound->Play(0.0f); while (true) { // Update the audio device AudioDevice->Update(true); // Sleep 33 ms FPlatformProcess::Sleep(DeltaTime); TimeCount += DeltaTime; if (TimeCount > 0.25f) { TimeCount = 0.0f; } } // Teardown the audio device AudioDevice->Teardown(); } } return bSuccess; } }; FPlayRealTimeSoundWaveLooping2D PlayRealTimeSoundWaveLooping2D; /************************************************************************ * FPlaySoundWaveLooping2DPitched ************************************************************************/ class FPlaySoundWaveLooping2DPitched final : public FAudioMixerCommand { public: FPlaySoundWaveLooping2DPitched() : FAudioMixerCommand(TEXT("PlaySoundWaveLooping2DPitched"), TEXT("Load and play a single looping 2D engine test sound wave using real-time decoding."), 1, TEXT("Number of loops you want to play")) {} bool Run(UWorld* World, const TArray& InArgs) override { // Load a single large seamless loop added to test path int32 NumLoops = 1; if (InArgs.Num()) { NumLoops = FMath::Max(FCString::Atoi(*InArgs[0]), 1); } FString LoopingSoundPath = FPaths::EngineContentDir() / TEXT("EngineSounds") / TEXT("TestSounds") / TEXT("Loops"); TArray SearchPaths; SearchPaths.Add(LoopingSoundPath); TArray SoundWaves; TArray IgnoreList; IgnoreList.Add(TEXT("WhiteNoise")); LoadEditorAndEngineObjects(SearchPaths, SoundWaves, &IgnoreList); FAudioDeviceHandle AudioDevice = GEngine->GetMainAudioDevice(); if (!AudioDevice) { return false; } FPlatformProcess::Sleep(1); const float DeltaTime = 0.033f; TArray AudioComponents; TArray PitchParams; TArray VolumeParams; TArray CurrentTime; TArray TargetTime; // Get the quality settings of the audio device (uses game user settings) int32 SoundCount = 0; AudioComponents.Reserve(NumLoops); for (int32 i = 0; i < NumLoops; ++i) { UAudioComponent* LoopingSound = SpawnLoopingSound(AudioDevice.GetAudioDevice(), World, SoundWaves); if (!LoopingSound) { return false; } AudioComponents.Add(LoopingSound); PitchParams.Add(FDynamicParameter(FMath::FRandRange(0.1f, 4.0f))); VolumeParams.Add(FDynamicParameter(0.0f)); float NewTargetTime = FMath::FRandRange(0.5f, 3.0f); PitchParams[i].Set(FMath::FRandRange(0.1f, 4.0f), NewTargetTime); VolumeParams[i].Set(FMath::FRandRange(0.1f, 1.0f), NewTargetTime); CurrentTime.Add(0.0f); TargetTime.Add(NewTargetTime); } while (true) { // Update the audio device AudioDevice->Update(true); for (int32 i = 0; i < NumLoops; ++i) { if (!AudioComponents[i]->IsActive()) { AudioComponents[i]->Play(0.0f); } AudioComponents[i]->SetPitchMultiplier(PitchParams[i].GetValue()); AudioComponents[i]->SetVolumeMultiplier(VolumeParams[i].GetValue()); PitchParams[i].Update(DeltaTime); VolumeParams[i].Update(DeltaTime); CurrentTime[i] += DeltaTime; if (CurrentTime[i] >= TargetTime[i]) { CurrentTime[i] = 0.0f; float NewTargetTime = FMath::FRandRange(2.0f, 3.0f); PitchParams[i].Set(FMath::FRandRange(0.1f, 4.0f), NewTargetTime); VolumeParams[i].Set(FMath::FRandRange(0.1f, 1.0f), NewTargetTime); TargetTime[i] = NewTargetTime; } } // Sleep 33 ms FPlatformProcess::Sleep(DeltaTime); } // Teardown the audio device AudioDevice->Teardown(); return true; } }; FPlaySoundWaveLooping2DPitched PlaySoundWaveLooping2DPitched; /************************************************************************ * FPlaySoundWaveLooping2DPitched ************************************************************************/ class FPlaySoundWaveLooping3DPitched final : public FAudioMixerCommand { public: FPlaySoundWaveLooping3DPitched() : FAudioMixerCommand("PlaySoundWaveLooping3DPitched", "Load and play a single looping 3D engine test sound wave using real-time decoding.", 1, "Number of loops you want to play") {} bool Run(UWorld* World, const TArray& InArgs) override { // Load a single large seamless loop added to test path int32 NumLoops = 1; if (InArgs.Num()) { NumLoops = FMath::Max(FCString::Atoi(*InArgs[0]), 1); } FString LoopingSoundPath = FPaths::EngineContentDir() / TEXT("EngineSounds") / TEXT("TestSounds") / TEXT("Loops") / TEXT("Mono"); TArray SearchPaths; SearchPaths.Add(LoopingSoundPath); TArray SoundWaves; TArray IgnoreList; IgnoreList.Add(TEXT("WhiteNoise")); LoadEditorAndEngineObjects(SearchPaths, SoundWaves, &IgnoreList); FString AttenuationSearchPath = FPaths::EngineContentDir() / TEXT("EngineSounds") / TEXT("TestSounds") / TEXT("Attenuation"); TArray SoundAttenuations; SearchPaths.Reset(); SearchPaths.Add(AttenuationSearchPath); LoadEditorAndEngineObjects(SearchPaths, SoundAttenuations); FAudioDeviceHandle AudioDevice = GEngine->GetMainAudioDevice(); if (!AudioDevice) { return false; } FPlatformProcess::Sleep(1); const float DeltaTime = 0.033f; TArray AudioComponents; TArray PitchParams; TArray VolumeParams; TArray CurrentTime; TArray TargetTime; TArray Rotators; // Get the quality settings of the audio device (uses game user settings) int32 SoundCount = 0; for (int32 i = 0; i < NumLoops; ++i) { int32 AttenuationIndex = FMath::RandRange(0, SoundAttenuations.Num() - 1); USoundAttenuation* Attenuation = SoundAttenuations[AttenuationIndex]; UAudioComponent* LoopingSound = SpawnLoopingSound(AudioDevice.GetAudioDevice(), World, SoundWaves, true, Attenuation); if (!LoopingSound) { return false; } AudioComponents.Add(LoopingSound); PitchParams.Add(FDynamicParameter(FMath::FRandRange(0.1f, 4.0f))); VolumeParams.Add(FDynamicParameter(0.0f)); Rotators.Add(FPositionRotator(FMath::FRandRange(50.0f, 1000.0f), FMath::FRandRange(0.0f, 2.0f*PI), FMath::FRandRange(-0.1f, 0.1f))); float NewTargetTime = FMath::FRandRange(0.5f, 3.0f); PitchParams[i].Set(FMath::FRandRange(0.1f, 4.0f), NewTargetTime); VolumeParams[i].Set(FMath::FRandRange(0.1f, 1.0f), NewTargetTime); CurrentTime.Add(0.0f); TargetTime.Add(NewTargetTime); } while (true) { // Update the audio device AudioDevice->Update(true); for (int32 i = 0; i < NumLoops; ++i) { // Update the position FPositionRotator& Rotator = Rotators[i]; Rotator.Update(); FVector Position = Rotator.GetPosition(); UE_LOG(LogTemp, Log, TEXT("Position - X: %.2f, Y: %.2f, Z: %.2f"), Position.X, Position.Y, Position.Z); AudioComponents[i]->SetWorldLocationAndRotation(Position, FRotator::ZeroRotator); AudioComponents[i]->SetPitchMultiplier(PitchParams[i].GetValue()); AudioComponents[i]->SetVolumeMultiplier(VolumeParams[i].GetValue()); if (!AudioComponents[i]->IsActive()) { AudioComponents[i]->Play(0.0f); } PitchParams[i].Update(DeltaTime); VolumeParams[i].Update(DeltaTime); CurrentTime[i] += DeltaTime; if (CurrentTime[i] >= TargetTime[i]) { CurrentTime[i] = 0.0f; float NewTargetTime = FMath::FRandRange(2.0f, 3.0f); PitchParams[i].Set(FMath::FRandRange(0.1f, 4.0f), NewTargetTime); VolumeParams[i].Set(FMath::FRandRange(0.1f, 1.0f), NewTargetTime); TargetTime[i] = NewTargetTime; } } // Sleep 33 ms FPlatformProcess::Sleep(DeltaTime); } // Teardown the audio device AudioDevice->Teardown(); return true; } }; FPlaySoundWaveLooping3DPitched PlaySoundWaveLooping3DPitched; #endif // ENABLE_AUDIO_MIXER_COMMANDLET /************************************************************************ * UAudioMixerCommandlet ************************************************************************/ UAudioMixerCommandlet::UAudioMixerCommandlet(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void UAudioMixerCommandlet::PrintUsage() const { #if ENABLE_AUDIO_MIXER_COMMANDLET UE_LOG(AudioMixerCommandlet, Display, TEXT("AudioMixerCommandlet Usage: {Editor}.exe UnrealEd.AudioMixerCommandlet {CommandName} {Args}")); UE_LOG(AudioMixerCommandlet, Display, TEXT("Possible commands:\n")); UE_LOG(AudioMixerCommandlet, Display, TEXT("Command Name, Command Description, Number of Arguments, Argument Description")); TArray& Commands = FAudioMixerCommand::GetCommands(); for (int32 i = 0; i < Commands.Num(); ++i) { const FAudioMixerCommand* MixerCommand = Commands[i]; UE_LOG(AudioMixerCommandlet, Display, TEXT("%s, %s, %d, %s"), *MixerCommand->GetName(), *MixerCommand->GetDescription(), MixerCommand->GetNumArgs(), *MixerCommand->GetArgDescription()); } #endif } int32 UAudioMixerCommandlet::Main(const FString& InParams) { #if ENABLE_AUDIO_MIXER_COMMANDLET TArray Tokens; TArray Switches; UCommandlet::ParseCommandLine(*InParams, Tokens, Switches); if (Tokens.Num() < 2) { PrintUsage(); return 0; } UWorld* World = UWorld::CreateWorld(EWorldType::Game, true); FWorldContext& WorldContext = GEngine->CreateNewWorldContext(EWorldType::Game); WorldContext.SetCurrentWorld(World); FURL URL; World->InitializeActorsForPlay(URL); World->BeginPlay(); // Get command name FString CommandName = Tokens[1]; bool bFoundTest = false; TArray& Commands = FAudioMixerCommand::GetCommands(); for (int32 i = 0; i < Commands.Num(); ++i) { if (Commands[i]->GetName() == CommandName) { bFoundTest = true; TArray Args; if (Commands[i]->GetNumArgs() > 0) { for (int32 j = 2; j < Tokens.Num(); ++j) { Args.Add(Tokens[j]); } } bool bSuccess = Commands[i]->Run(World, Args); UE_LOG(AudioMixerCommandlet, Display, TEXT("Command %s %s."), *Commands[i]->GetName(), bSuccess ? TEXT("succeeded") : TEXT("failed")); break; } } GEngine->DestroyWorldContext(World); World->DestroyWorld(true); if (!bFoundTest) { UE_LOG(AudioMixerCommandlet, Display, TEXT("Unknown test '%s'. Exiting."), *CommandName); return 0; } #endif // ENABLE_AUDIO_MIXER_COMMANDLET return 0; }