123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- /*******************************************************************************
- The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
- Technology released in source code form as part of the game integration package.
- The content of this file may not be used without valid licenses to the
- AUDIOKINETIC Wwise Technology.
- Note that the use of the game engine is subject to the Unreal(R) Engine End User
- License Agreement at https://www.unrealengine.com/en-US/eula/unreal
-
- License Usage
-
- Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
- this file in accordance with the end user license agreement provided with the
- software or, alternatively, in accordance with the terms contained
- in a written agreement between you and Audiokinetic Inc.
- Copyright (c) 2023 Audiokinetic Inc.
- *******************************************************************************/
- #include "AkSoundBankGenerationManager.h"
- #include "AkAudioBankGenerationHelpers.h"
- #include "AkAudioDevice.h"
- #include "AkAudioStyle.h"
- #include "AkWaapiClient.h"
- #include "AkWaapiUtils.h"
- #include "IAudiokineticTools.h"
- #include "Async/Async.h"
- #include "Framework/Docking/TabManager.h"
- #include "Framework/Notifications/NotificationManager.h"
- #if UE_5_0_OR_LATER
- #include "HAL/PlatformFileManager.h"
- #else
- #include "HAL/PlatformFilemanager.h"
- #endif
- #include "AkAudioModule.h"
- #include "Misc/ScopeExit.h"
- #include "Internationalization/Text.h"
- #include "Wwise/WwiseProjectDatabase.h"
- #define LOCTEXT_NAMESPACE "AkAudio"
- DECLARE_CYCLE_STAT(TEXT("AkSoundData - Waapi Call"), STAT_WaapiCall, STATGROUP_AkSoundBankGenerationSource);
- AkSoundBankGenerationManager::AkSoundBankGenerationManager(const FInitParameters& InitParameters)
- : InitParameters(InitParameters)
- {
- PlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
- }
- AkSoundBankGenerationManager::~AkSoundBankGenerationManager()
- {
- }
- void AkSoundBankGenerationManager::SetIsBuilding(bool bIsBuilding)
- {
- bIsBuildingData = bIsBuilding;
- }
- void AkSoundBankGenerationManager::Init()
- {
- }
- void AkSoundBankGenerationManager::DoGeneration()
- {
- if (bIsBuildingData)
- {
- Notify(TEXT("SoundBankGenerationAborted"),
- TEXT("Wwise SoundBank generation aborted, there is already a generation task in progress!"),
- TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"),
- true);
- return;
- }
- SetIsBuilding(true);
- StartTime = FPlatformTime::Cycles64();
- if (InitParameters.GenerationMode != ESoundBankGenerationMode::Commandlet)
- {
- CreateNotificationItem();
- }
- bool bGenerationSuccess = false;
- switch (InitParameters.GenerationMode)
- {
- #if AK_SUPPORT_WAAPI
- case ESoundBankGenerationMode::WAAPI:
- bGenerationSuccess = WAAPIGenerate();
- break;
- #endif
- case ESoundBankGenerationMode::WwiseConsole:
- case ESoundBankGenerationMode::Commandlet:
- default:
- bGenerationSuccess = WwiseConsoleGenerate();
- break;
- }
- WrapUpGeneration(bGenerationSuccess, TEXT("WwiseConsole"));
- }
- void AkSoundBankGenerationManager::WrapUpGeneration(const bool bSuccess, const FString& BuilderName)
- {
- SetIsBuilding(false);
- FString SuccessMessage;
- if(!bSuccess)
- {
- SuccessMessage = BuilderName + TEXT(" Sound Data Builder task failed");
- NotifyGenerationFailed();
- }
- else
- {
- SuccessMessage = BuilderName + TEXT(" Sound Data Builder task was successful");
- NotifyGenerationSucceeded();
- }
- auto EndTime = FPlatformTime::Cycles64();
- UE_LOG(LogAudiokineticTools, Display, TEXT("%s and took %f seconds."), *SuccessMessage,
- FPlatformTime::ToSeconds64(EndTime - StartTime));
- FWwiseProjectDatabase* ProjectDatabase = FWwiseProjectDatabase::Get();
- if(UNLIKELY(!ProjectDatabase))
- {
- return;
- }
- FAkAudioModule::AkAudioModuleInstance->UpdateWwiseResourceLoaderSettings();
- ProjectDatabase->UpdateDataStructure();
- }
- void AkSoundBankGenerationManager::CreateNotificationItem()
- {
- if (InitParameters.GenerationMode != ESoundBankGenerationMode::Commandlet)
- {
- AsyncTask(ENamedThreads::Type::GameThread, [sharedThis = SharedThis(this)]
- {
- FNotificationInfo Info(LOCTEXT("GeneratingSoundBanks", "Generating Wwise SoundBanks..."));
- Info.Image = FAkAudioStyle::GetBrush(TEXT("AudiokineticTools.AkBrowserTabIcon"));
- Info.bFireAndForget = false;
- Info.FadeOutDuration = 0.0f;
- Info.ExpireDuration = 0.0f;
- #if UE_4_26_OR_LATER
- Info.Hyperlink = FSimpleDelegate::CreateLambda([]() { FGlobalTabmanager::Get()->TryInvokeTab(FName("OutputLog")); });
- #else
- Info.Hyperlink = FSimpleDelegate::CreateLambda([]() { FGlobalTabmanager::Get()->InvokeTab(FName("OutputLog")); });
- #endif
- Info.HyperlinkText = LOCTEXT("ShowOutputLogHyperlink", "Show Output Log");
- sharedThis->NotificationItem = FSlateNotificationManager::Get().AddNotification(Info);
- });
- }
- }
- void AkSoundBankGenerationManager::Notify(const FString& key, const FString& message, const FString& AudioCuePath, bool bSuccess)
- {
- if (InitParameters.GenerationMode != ESoundBankGenerationMode::Commandlet)
- {
- AsyncTask(ENamedThreads::Type::GameThread, [sharedThis = SharedThis(this), key, message, AudioCuePath, bSuccess]
- {
- if (sharedThis->NotificationItem)
- {
- const FTextId TextId(TEXT(LOCTEXT_NAMESPACE), key);
- FText LocText = FText::ChangeKey(TextId.GetNamespace(), TextId.GetKey(), FText::FromString(message));
- sharedThis->NotificationItem->SetText(LocText);
- sharedThis->NotificationItem->SetCompletionState(
- bSuccess ? SNotificationItem::CS_Success : SNotificationItem::CS_Fail);
- sharedThis->NotificationItem->SetExpireDuration(3.5f);
- sharedThis->NotificationItem->SetFadeOutDuration(0.5f);
- sharedThis->NotificationItem->ExpireAndFadeout();
- }
- GEditor->PlayEditorSound(AudioCuePath);
- });
- }
- }
- void AkSoundBankGenerationManager::NotifyGenerationSucceeded()
- {
- Notify(TEXT("SoundBankGenerationCompleted"),
- TEXT("Wwise SoundBank generation completed!"),
- TEXT("/Engine/EditorSounds/Notifications/CompileSuccess_Cue.CompileSuccess_Cue"),
- true);
- }
- void AkSoundBankGenerationManager::NotifyGenerationFailed()
- {
- Notify(TEXT("SoundBankGenerationFailed"),
- TEXT("Generating Wwise SoundBanks failed!"),
- TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"),
- false);
- }
- void AkSoundBankGenerationManager::NotifyProfilingInProgress()
- {
- Notify(TEXT("SoundBankGenerationProfiling"),
- TEXT("Cannot generate SoundBanks while Authoring is profiling."),
- TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"),
- false);
- }
- void AkSoundBankGenerationManager::NotifyAuthoringUnavailable()
- {
- Notify(
- TEXT("SoundBankGenerationAuthoringLocked"),
- TEXT("Cannot generate SoundBanks while Authoring is in its current state."),
- TEXT("/Engine/EditorSounds/Notifications/CompileFailed_Cue.CompileFailed_Cue"),
- false);
- }
- bool AkSoundBankGenerationManager::WwiseConsoleGenerate()
- {
- FString WwiseConsoleCommand = OverrideWwiseConsolePath.IsEmpty() ? AkAudioBankGenerationHelper::GetWwiseConsoleApplicationPath() : OverrideWwiseConsolePath;
- FString WwiseConsoleArguments;
- #if PLATFORM_MAC
- WwiseConsoleArguments = WwiseConsoleCommand + TEXT(" ");
- WwiseConsoleCommand = TEXT("/bin/sh");
- #endif
- WwiseConsoleArguments += FString::Printf(TEXT("generate-soundbank \"%s\" --use-stable-guid "),
- *PlatformFile->ConvertToAbsolutePathForExternalAppForWrite(*WwiseUnrealHelper::GetWwiseProjectPath()));
- auto GeneratedSoundBanksPath = WwiseUnrealHelper::GetSoundBankDirectory();
- if (InitParameters.Platforms.Num() > 0)
- {
- WwiseConsoleArguments += FString::Printf(TEXT(" --platform"));
- for (auto& Platform : InitParameters.Platforms)
- {
- WwiseConsoleArguments += FString::Printf(TEXT(" \"%s\""), *Platform);
- }
- }
- if (InitParameters.SkipLanguages)
- {
- WwiseConsoleArguments += TEXT(" --skip-languages");
- }
- else
- {
- if (InitParameters.Languages.Num() > 0)
- {
- WwiseConsoleArguments += FString::Printf(TEXT(" --language"));
- for (auto& Language : InitParameters.Languages)
- {
- WwiseConsoleArguments += FString::Printf(TEXT(" \"%s\""), *Language);
- }
- }
- }
- return RunWwiseConsole(WwiseConsoleCommand, WwiseConsoleArguments);
- }
- bool AkSoundBankGenerationManager::RunWwiseConsole(const FString& WwiseConsoleCommand,const FString& WwiseConsoleArguments)
- {
- UE_LOG(LogAudiokineticTools, Display, TEXT("Running WwiseConsole command : %s %s"), *WwiseConsoleCommand, *WwiseConsoleArguments);
- bool bGenerationSuccess = true;
- // Create a pipe for the child process's STDOUT.
- int32 ReturnCode = 0;
- void* WritePipe = nullptr;
- void* ReadPipe = nullptr;
- FPlatformProcess::CreatePipe(ReadPipe, WritePipe);
- ON_SCOPE_EXIT{
- FPlatformProcess::ClosePipe(ReadPipe, WritePipe);
- };
- FProcHandle ProcHandle = FPlatformProcess::CreateProc(*WwiseConsoleCommand, *WwiseConsoleArguments, true, true, true, nullptr, 0, nullptr, WritePipe);
- if (ProcHandle.IsValid())
- {
- FString NewLine;
- FPlatformProcess::Sleep(0.1f);
- // Wait for it to finish and get return code
- while (FPlatformProcess::IsProcRunning(ProcHandle))
- {
- NewLine = FPlatformProcess::ReadPipe(ReadPipe);
- if (NewLine.Len() > 0)
- {
- UE_LOG(LogAudiokineticTools, Display, TEXT("%s"), *NewLine);
- NewLine.Empty();
- }
- FPlatformProcess::Sleep(0.25f);
- }
- NewLine = FPlatformProcess::ReadPipe(ReadPipe);
- if (NewLine.Len() > 0)
- {
- UE_LOG(LogAudiokineticTools, Display, TEXT("%s"), *NewLine);
- }
- FPlatformProcess::GetProcReturnCode(ProcHandle, &ReturnCode);
- switch (ReturnCode)
- {
- case 0:
- UE_LOG(LogAudiokineticTools, Display, TEXT("WwiseConsole successfully completed."));
- break;
- case 2:
- UE_LOG(LogAudiokineticTools, Warning, TEXT("WwiseConsole completed with warnings :\n %s %s"), *WwiseConsoleCommand, *WwiseConsoleArguments);
- break;
- default:
- UE_LOG(LogAudiokineticTools, Error, TEXT("WwiseConsole failed with error %d :\n %s %s"), ReturnCode, *WwiseConsoleCommand, *WwiseConsoleArguments);
- bGenerationSuccess = false;
- break;
- }
- }
- else
- {
- bGenerationSuccess = false;
- ReturnCode = -1;
- // Most chances are the path to the .exe or the project were not set properly in GEditorIni file.
- UE_LOG(LogAudiokineticTools, Error, TEXT("Failed to run WwiseConsole:\n %s %s"), *WwiseConsoleCommand, *WwiseConsoleArguments);
- }
- return bGenerationSuccess;
- }
- #if AK_SUPPORT_WAAPI
- bool AkSoundBankGenerationManager::WAAPIGenerate()
- {
- if (!SubscribeToGenerationDone())
- {
- return false;
- }
- ON_SCOPE_EXIT
- {
- CleanupWaapiSubscriptions();
- };
- TSharedRef<FJsonObject> args = MakeShared<FJsonObject>();
- TSharedRef<FJsonObject> options = MakeShared<FJsonObject>();
- TSharedPtr<FJsonObject> result;
- if (FAkWaapiClient::Get()->Call(ak::wwise::core::remote::getConnectionStatus, args, options, result, -1))
- {
- bool isConnected = false;
- if (result->TryGetBoolField(WwiseWaapiHelper::IS_CONNECTED, isConnected) && isConnected)
- {
- NotifyAuthoringUnavailable();
- return false;
- }
- }
- TArray<TSharedPtr<FJsonValue>> platformJsonArray;
- for (auto& platform : InitParameters.Platforms)
- {
- platformJsonArray.Add(MakeShared<FJsonValueString>(platform));
- }
- args->SetArrayField(WwiseWaapiHelper::PLATFORMS, platformJsonArray);
- if (InitParameters.Languages.Num() > 0)
- {
- TArray<TSharedPtr<FJsonValue>> LanguageJsonArray;
- for (auto& Language : InitParameters.Languages)
- {
- LanguageJsonArray.Add(MakeShared<FJsonValueString>(Language));
- }
- args->SetArrayField(WwiseWaapiHelper::LANGUAGES, LanguageJsonArray);
- }
- args->SetBoolField(WwiseWaapiHelper::SKIP_LANGUAGES, InitParameters.SkipLanguages);
- args->SetBoolField(WwiseWaapiHelper::WRITE_TO_DISK, true);
- // do we always want to rebuild init bank now?
- args->SetBoolField(WwiseWaapiHelper::REBUILD_INIT_BANK, true);
- bool WaapiCallSuccess = false;
- {
- SCOPE_CYCLE_COUNTER(STAT_WaapiCall);
- WaapiCallSuccess = FAkWaapiClient::Get()->Call(ak::wwise::core::soundbank::generate, args, options, result, -1);
- if (!WaapiCallSuccess)
- {
- auto Message = result->GetStringField(WwiseWaapiHelper::MESSSAGE);
- UE_LOG(LogAkAudio, Error, TEXT("WAAPI Sound Data generation failed: %s"), *Message);
- }
- }
- if (WaapiCallSuccess)
- {
- WaitForGenerationDoneEvent->Wait();
- }
- return WaapiGenerationSuccess;
- }
- bool AkSoundBankGenerationManager::SubscribeToGenerationDone()
- {
- TSharedPtr<FJsonObject> Result;
- TSharedRef<FJsonObject> DoneOptions = MakeShared<FJsonObject>();
- auto SoundBankGenerationDoneCallback = WampEventCallback::CreateRaw(this, &AkSoundBankGenerationManager::OnSoundBankGenerationDone);
- FAkWaapiClient::Get()->Subscribe(ak::wwise::core::soundbank::generationDone, DoneOptions, SoundBankGenerationDoneCallback, GenerationDoneSubscriptionId, Result);
- WaitForGenerationDoneEvent = FGenericPlatformProcess::GetSynchEventFromPool();
- return GenerationDoneSubscriptionId != 0;
- }
- void AkSoundBankGenerationManager::CleanupWaapiSubscriptions()
- {
- TSharedPtr<FJsonObject> result;
- FAkWaapiClient::Get()->Unsubscribe(GenerationDoneSubscriptionId, result);
- FGenericPlatformProcess::ReturnSynchEventToPool(WaitForGenerationDoneEvent);
- }
- void AkSoundBankGenerationManager::OnSoundBankGenerationDone(uint64_t id, TSharedPtr<FJsonObject> responseJson)
- {
- const TArray<TSharedPtr<FJsonValue>>* logs = nullptr;
- if (responseJson->TryGetArrayField(TEXT("logs"), logs))
- {
- for (auto& entry : *logs)
- {
- const TSharedPtr<FJsonObject>* jsonEntry = nullptr;
- if (entry->TryGetObject(jsonEntry))
- {
- const auto severity = jsonEntry->Get()->GetStringField(TEXT("severity"));
- const auto message = jsonEntry->Get()->GetStringField(WwiseWaapiHelper::MESSSAGE);
- FString platform = "";
- const TSharedPtr<FJsonObject>* jsonPlatform = nullptr;
- if (jsonEntry->Get()->TryGetObjectField(TEXT("platform"), jsonPlatform))
- {
- jsonPlatform->Get()->TryGetStringField(WwiseWaapiHelper::NAME, platform);
- }
- if (severity == TEXT("Message"))
- {
- UE_LOG(LogAudiokineticTools, Display, TEXT("%s: %s"), *platform, *message);
- }
- else if (severity == TEXT("Warning"))
- {
- UE_LOG(LogAudiokineticTools, Warning, TEXT("%s: %s"), *platform, *message);
- }
- else if (severity == TEXT("Error") || severity == TEXT("Fatal Error"))
- {
- WaapiGenerationSuccess = false;
- UE_LOG(LogAudiokineticTools, Error, TEXT("%s: %s"), *platform, *message);
- }
- }
- }
- }
- else
- {
- FString outError;
- if (responseJson->TryGetStringField(TEXT("error"), outError))
- {
- WaapiGenerationSuccess = false;
- UE_LOG(LogAudiokineticTools, Error, TEXT("%s"), *outError);
- }
- }
- WaitForGenerationDoneEvent->Trigger();
- }
- #endif
- #undef LOCTEXT_NAMESPACE
|