1222 lines
42 KiB
C++
1222 lines
42 KiB
C++
/*******************************************************************************
|
|
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.
|
|
*******************************************************************************/
|
|
|
|
/*=============================================================================
|
|
AkWaapiClient.cpp: Audiokinetic WAAPI interface object.
|
|
|
|
Unreal is RHS with Y and Z swapped (or technically LHS with flipped axis)
|
|
|
|
=============================================================================*/
|
|
|
|
/*------------------------------------------------------------------------------------
|
|
Audio includes.
|
|
------------------------------------------------------------------------------------*/
|
|
#include "AkWaapiClient.h"
|
|
#include "AkAudioDevice.h"
|
|
#include "AkSettings.h"
|
|
#include "AkSettingsPerUser.h"
|
|
#include "AkUnrealHelper.h"
|
|
#include "Serialization/JsonSerializer.h"
|
|
#include "Async/Async.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "Misc/CoreDelegates.h"
|
|
#include "Wwise/API/WAAPI.h"
|
|
|
|
#if AK_SUPPORT_WAAPI
|
|
|
|
#if PLATFORM_WINDOWS
|
|
// Problem with the Windows API as a whole: it uses noexcept straight up, whether exceptions are used or not, generating a warning that Unreal then converts to an error.
|
|
#if _MSC_VER >= 1910
|
|
#pragma warning (disable: 4577)
|
|
#endif // _MSC_VER >= 1910
|
|
#endif // #if PLATFORM_WINDOWS
|
|
|
|
typedef AK::WwiseAuthoringAPI::JsonProvider JsonProvider;
|
|
#endif
|
|
|
|
/*------------------------------------------------------------------------------------
|
|
Statics and Globals
|
|
------------------------------------------------------------------------------------*/
|
|
FAkWaapiClient* g_AkWaapiClient = nullptr;
|
|
|
|
/*------------------------------------------------------------------------------------
|
|
FAkWaapiClientConnectionHandler
|
|
------------------------------------------------------------------------------------*/
|
|
|
|
FAkWaapiClientConnectionHandler::FAkWaapiClientConnectionHandler(FAkWaapiClient& in_Client) : m_Client(in_Client)
|
|
{
|
|
WaitEvent = FPlatformProcess::GetSynchEventFromPool(true);
|
|
}
|
|
|
|
FAkWaapiClientConnectionHandler::~FAkWaapiClientConnectionHandler()
|
|
{
|
|
FPlatformProcess::ReturnSynchEventToPool(WaitEvent);
|
|
WaitEvent = nullptr;
|
|
}
|
|
|
|
void FAkWaapiClientConnectionHandler::RegisterAutoConnectChangedCallback()
|
|
{
|
|
#if WITH_EDITOR
|
|
FScopeLock Lock(&AkSettingsSection);
|
|
if (auto AkSettingsPerUser = GetDefault<UAkSettingsPerUser>())
|
|
{
|
|
AutoConnectChangedHandle = AkSettingsPerUser->OnAutoConnectToWaapiChanged.AddLambda([this, AkSettingsPerUser]()
|
|
{
|
|
ResetReconnectionDelay();
|
|
if (AkSettingsPerUser->bAutoConnectToWAAPI)
|
|
WaitEvent->Trigger();
|
|
else
|
|
{
|
|
m_Client.BroadcastConnectionLost();
|
|
}
|
|
});
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FAkWaapiClientConnectionHandler::Wake()
|
|
{
|
|
WaitEvent->Trigger();
|
|
}
|
|
|
|
/* FRunnable interface */
|
|
bool FAkWaapiClientConnectionHandler::Init()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
uint32 FAkWaapiClientConnectionHandler::Run()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
AKASSERT(!IsInGameThread());
|
|
while (!ThreadShouldExit)
|
|
{
|
|
if (!m_Client.IsProjectLoaded())
|
|
{
|
|
/** Check if we should attempt to reconnect according to the Wwise Plugin Settings. */
|
|
bool bReconnect = !m_Client.IsDisconnecting() && !m_Client.AppIsExiting();
|
|
{
|
|
FScopeLock Lock(&AkSettingsSection);
|
|
if (auto AkSettingsPerUser = GetDefault<UAkSettingsPerUser>())
|
|
{
|
|
bReconnect = AkSettingsPerUser->bAutoConnectToWAAPI;
|
|
}
|
|
}
|
|
/** If we previously had a connection (and we're not exiting), broadcast connection lost.*/
|
|
if (hadConnection && !m_Client.AppIsExiting())
|
|
{
|
|
if (bReconnect)
|
|
UE_LOG(LogAkAudio, Warning, TEXT("Lost connection to WAAPI client. Attempting reconnection ..."));
|
|
hadConnection = false;
|
|
AsyncTask(ENamedThreads::GameThread, [this]()
|
|
{
|
|
m_Client.BroadcastConnectionLost();
|
|
});
|
|
}
|
|
/** If we should reconnect, attempt a reconnection and, if successful, call the client's connection established function on the game thread.
|
|
* Otherwise, print a failed connection log.
|
|
*/
|
|
if (bReconnect)
|
|
{
|
|
if (AttemptReconnect())
|
|
{
|
|
hadConnection = true;
|
|
m_Client.SetConnectionClosing(false);
|
|
ResetReconnectionDelay();
|
|
AsyncTask(ENamedThreads::GameThread, [this]()
|
|
{
|
|
m_Client.ConnectionEstablished();
|
|
});
|
|
}
|
|
else
|
|
{
|
|
if (LogOutputCount.GetValue() < 7)
|
|
{
|
|
UE_LOG(LogAkAudio, Warning, TEXT("Failed to connect to WAAPI client on local host. Trying again in %i seconds."), ReconnectDelay.GetValue());
|
|
LogOutputCount.Increment();
|
|
}
|
|
}
|
|
/** Delay the next reconnection attempt according to the ReconnectDelay value. */
|
|
const int iCurrentDelay = ReconnectDelay.GetValue();
|
|
if (iCurrentDelay < m_iMaxReconnectDelay)
|
|
ReconnectDelay.Set(iCurrentDelay * 2);
|
|
WaitEvent->Wait(iCurrentDelay * 1000);
|
|
WaitEvent->Reset();
|
|
}
|
|
else
|
|
{
|
|
/** We shouldn't attempt reconnection, so wait until the auto-reconnect option is changed. */
|
|
WaitEvent->Wait();
|
|
WaitEvent->Reset();
|
|
}
|
|
|
|
}
|
|
else /** We're already connected. Check connection status. */
|
|
{
|
|
TSharedRef<FJsonObject> args = MakeShareable(new FJsonObject());
|
|
TSharedRef<FJsonObject> options = MakeShareable(new FJsonObject());
|
|
TSharedPtr<FJsonObject> result = MakeShareable(new FJsonObject());
|
|
m_Client.Call(ak::wwise::core::getInfo, args, options, result, 500, true);
|
|
WaitEvent->Wait(ConnectionMonitorDelay.GetValue() * 1000);
|
|
WaitEvent->Reset();
|
|
}
|
|
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void FAkWaapiClientConnectionHandler::ResetReconnectionDelay()
|
|
{
|
|
bool bReconnect = true;
|
|
{
|
|
FScopeLock Lock(&AkSettingsSection);
|
|
if (auto AkSettingsPerUser = GetDefault<UAkSettingsPerUser>())
|
|
{
|
|
bReconnect = AkSettingsPerUser->bAutoConnectToWAAPI;
|
|
}
|
|
}
|
|
if (bReconnect && !m_Client.AppIsExiting() && !m_Client.IsDisconnecting())
|
|
{
|
|
ReconnectDelay.Set(2);
|
|
LogOutputCount.Set(0);
|
|
}
|
|
}
|
|
|
|
void FAkWaapiClientConnectionHandler::Stop()
|
|
{
|
|
ThreadShouldExit = true;
|
|
}
|
|
|
|
void FAkWaapiClientConnectionHandler::Exit()
|
|
{
|
|
ThreadShouldExit = true;
|
|
}
|
|
|
|
bool FAkWaapiClientConnectionHandler::AttemptReconnect()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
if (m_Client.AttemptConnection())
|
|
{
|
|
UE_LOG(LogAkAudio, Log, TEXT("Successfully connected to Wwise Authoring on localhost."));
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------------
|
|
Helpers
|
|
------------------------------------------------------------------------------------*/
|
|
struct FAkWaapiClientImpl
|
|
{
|
|
void Init(FAkWaapiClient& in_Client)
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
auto* WAAPI = IWAAPI::Get();
|
|
if (UNLIKELY(!WAAPI))
|
|
{
|
|
return;
|
|
}
|
|
m_Client = WAAPI->NewClient();
|
|
if (UNLIKELY(!m_Client))
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_pConnectionHandler = MakeShareable(new FAkWaapiClientConnectionHandler(in_Client));
|
|
FString ThreadName(FString::Printf(TEXT("WAAPIClientConnectionThread%i"), ThreadCounter.Increment()));
|
|
m_pReconnectionThread = MakeShareable(FRunnableThread::Create(m_pConnectionHandler.Get(),
|
|
*ThreadName, 0,
|
|
EThreadPriority::TPri_BelowNormal));
|
|
|
|
m_pConnectionHandler->RegisterAutoConnectChangedCallback();
|
|
#endif
|
|
}
|
|
|
|
~FAkWaapiClientImpl()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
if (m_pConnectionHandler.IsValid())
|
|
{
|
|
m_pConnectionHandler->Exit();
|
|
m_pConnectionHandler->Wake();
|
|
}
|
|
|
|
if (m_pReconnectionThread.IsValid())
|
|
{
|
|
if (!m_pReconnectionThread->Kill(true))
|
|
{
|
|
UE_LOG(LogAkAudio, Error, TEXT("WAAPI Connection Thread Failed to Exit!"));
|
|
}
|
|
}
|
|
|
|
delete m_Client; m_Client = nullptr;
|
|
#endif
|
|
}
|
|
|
|
#if AK_SUPPORT_WAAPI
|
|
/** Map containing id keys and WampEventCallback values. */
|
|
TMap<uint64_t, WampEventCallback> m_wampEventCallbackMap;
|
|
IWAAPI::Client* m_Client = nullptr;
|
|
/** A non-0 value indicates that UE is exiting. */
|
|
FThreadSafeCounter AppExitingCounter = 0;
|
|
FThreadSafeCounter ThreadCounter;
|
|
/** Thread on which the WAAPI connection is monitored. */
|
|
TSharedPtr<FRunnableThread> m_pReconnectionThread;
|
|
/** The connection to WAAPI is monitored by this connection handler.
|
|
* It tries to reconnect when connection is lost, and continuously polls WAAPI for the connection status when WAAPI is connected.
|
|
* This behaviour can be disabled in AkSettings using the AutoConnectToWaapi boolean option.
|
|
*/
|
|
TSharedPtr<FAkWaapiClientConnectionHandler> m_pConnectionHandler;
|
|
FCriticalSection ClientSection;
|
|
|
|
/** Flag indicating whether the correct project has been loaded (it's "correct" if it matches the Project Path in AkSettings.) */
|
|
FThreadSafeBool bProjectLoaded = false;
|
|
|
|
/** Flag indicating if the connection is being killed and shouldn't be restarted. */
|
|
FThreadSafeBool bIsConnectionClosing = false;
|
|
#endif
|
|
};
|
|
|
|
bool FAkWaapiClient::JsonObjectToString(const TSharedRef<FJsonObject>& in_jsonObject, FString& ou_jsonObjectString)
|
|
{
|
|
TSharedRef<TJsonWriter<>> JsonWriterArgs = TJsonWriterFactory<>::Create(&ou_jsonObjectString);
|
|
auto result = FJsonSerializer::Serialize(in_jsonObject, JsonWriterArgs);
|
|
if (!result)
|
|
{
|
|
UE_LOG(LogAkAudio, Log, TEXT("Unable to get a string representation of the Json Object."));
|
|
}
|
|
JsonWriterArgs->Close();
|
|
return result;
|
|
}
|
|
|
|
#if AK_SUPPORT_WAAPI
|
|
void WampEventCallbacks(const uint64_t& in_subscriptionId, const JsonProvider& in_rJson)
|
|
{
|
|
if (g_AkWaapiClient == nullptr)
|
|
return;
|
|
|
|
auto wampEventCallbacks = g_AkWaapiClient->GetWampEventCallback(in_subscriptionId);
|
|
if (!wampEventCallbacks)
|
|
return;
|
|
|
|
auto* WAAPI = IWAAPI::Get();
|
|
if (UNLIKELY(!WAAPI))
|
|
{
|
|
return;
|
|
}
|
|
|
|
TSharedPtr<FJsonObject> ueJsonObject;
|
|
|
|
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(UTF8_TO_TCHAR(WAAPI->GetJsonString(in_rJson).c_str()));
|
|
if (!FJsonSerializer::Deserialize(Reader, ueJsonObject) || !ueJsonObject.IsValid())
|
|
{
|
|
UE_LOG(LogAkAudio, Log, TEXT("Unable to deserialize a JSON object from the string : %s"), UTF8_TO_TCHAR(WAAPI->GetJsonString(in_rJson).c_str()));
|
|
return;
|
|
}
|
|
|
|
wampEventCallbacks->Execute(in_subscriptionId, ueJsonObject);
|
|
}
|
|
#endif
|
|
|
|
|
|
/*------------------------------------------------------------------------------------
|
|
Implementation.
|
|
------------------------------------------------------------------------------------*/
|
|
|
|
FAkWaapiClient::~FAkWaapiClient()
|
|
{
|
|
delete m_Impl;
|
|
}
|
|
|
|
void FAkWaapiClient::Initialize()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
if (!g_AkWaapiClient)
|
|
{
|
|
g_AkWaapiClient = new FAkWaapiClient();
|
|
if(g_AkWaapiClient)
|
|
{
|
|
g_AkWaapiClient->m_Impl->Init(*g_AkWaapiClient);
|
|
}
|
|
FCoreDelegates::OnPreExit.AddLambda([]
|
|
{
|
|
if (g_AkWaapiClient != nullptr)
|
|
{
|
|
g_AkWaapiClient->m_Impl->AppExitingCounter.Increment();
|
|
TArray<uint64_t> aSubscriptionIDs;
|
|
g_AkWaapiClient->m_Impl->m_wampEventCallbackMap.GetKeys(aSubscriptionIDs);
|
|
TSharedPtr<FJsonObject> jsonResult = MakeShareable(new FJsonObject());
|
|
for (auto iSubscriptionID : aSubscriptionIDs)
|
|
{
|
|
g_AkWaapiClient->Unsubscribe(iSubscriptionID, jsonResult);
|
|
}
|
|
}
|
|
DeleteInstance();
|
|
});
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/** Returns the singleton instance of FAkWaapiClient. Be sure to call FAkWaapiClient::Initialize() first (i.e. during Module startup). */
|
|
FAkWaapiClient* FAkWaapiClient::Get()
|
|
{
|
|
return g_AkWaapiClient;
|
|
}
|
|
|
|
bool FAkWaapiClient::AppIsExiting()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
return m_Impl->AppExitingCounter.GetValue() != 0;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void FAkWaapiClient::SetConnectionClosing(bool isClosing)
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
m_Impl->bIsConnectionClosing = isClosing;
|
|
#endif
|
|
}
|
|
|
|
void FAkWaapiClient::DeleteInstance()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
if (g_AkWaapiClient == nullptr)
|
|
return;
|
|
|
|
g_AkWaapiClient->OnClientBeginDestroy.Broadcast();
|
|
g_AkWaapiClient->m_Impl->bIsConnectionClosing = true;
|
|
if (g_AkWaapiClient->m_Impl->m_Client)
|
|
{
|
|
g_AkWaapiClient->m_Impl->m_Client->Disconnect();
|
|
}
|
|
delete g_AkWaapiClient;
|
|
g_AkWaapiClient = nullptr;
|
|
#endif
|
|
}
|
|
|
|
bool FAkWaapiClient::IsDisconnecting()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
return m_Impl->bIsConnectionClosing;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
WampEventCallback* FAkWaapiClient::GetWampEventCallback(const uint64_t& in_subscriptionId)
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
return m_Impl->m_wampEventCallbackMap.Find(in_subscriptionId);
|
|
#else
|
|
return nullptr;
|
|
#endif
|
|
}
|
|
|
|
bool FAkWaapiClient::IsProjectLoaded()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
if (g_AkWaapiClient == nullptr)
|
|
return false;
|
|
|
|
if (!g_AkWaapiClient->IsConnected())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return g_AkWaapiClient->m_Impl->bProjectLoaded;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/** This is called when the reconnection handler successfully connects to WAAPI.
|
|
* We check if the correct project is loaded on a background thread. If it is, we broadcast OnProjectLoaded.
|
|
* We also subscribe to ak::wwise::core::project::loaded in order to check the project whenever one is loaded.
|
|
* If an incorrect project is loaded we broadcast OnConnectionLost.
|
|
*/
|
|
void FAkWaapiClient::ConnectionEstablished()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
ensure(IsInGameThread());
|
|
if (g_AkWaapiClient == nullptr)
|
|
return;
|
|
|
|
// Broadcast OnProjectLoaded. If we are here, it means connection was successful, and that the correct project is
|
|
// loaded and available
|
|
OnProjectLoaded.Broadcast();
|
|
|
|
//We also subscribe to ak::wwise::core::project::loaded in order to check the project whenever one is loaded.
|
|
auto projectLoadedCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr<FJsonObject> in_UEJsonObject)
|
|
{
|
|
AsyncTask(ENamedThreads::GameThread, [this, id, in_UEJsonObject]()
|
|
{
|
|
m_Impl->bProjectLoaded = CheckProjectLoaded();
|
|
if (m_Impl->bProjectLoaded)
|
|
{
|
|
OnProjectLoaded.Broadcast();
|
|
}
|
|
else if (!AppIsExiting())//If an incorrect project is loaded we broadcast OnConnectionLost
|
|
{
|
|
BroadcastConnectionLost();
|
|
}
|
|
});
|
|
});
|
|
|
|
TSharedPtr<FJsonObject> projectLoadedSubscriptionResult = MakeShareable(new FJsonObject());
|
|
TSharedRef<FJsonObject> projectLoadedOptions = MakeShareable(new FJsonObject());
|
|
uint64_t projectLoadedSubscriptionID;
|
|
g_AkWaapiClient->Subscribe(ak::wwise::core::project::loaded, projectLoadedOptions, projectLoadedCallback, projectLoadedSubscriptionID, projectLoadedSubscriptionResult);
|
|
|
|
//And we need to subscribe to ak::wwise::core::project::postClosed such that we are able to re-connect to WAAPI (for example if Wwise is closed then opened again).
|
|
auto projectClosedCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr<FJsonObject> in_UEJsonObject)
|
|
{
|
|
AsyncTask(ENamedThreads::GameThread, [this]()
|
|
{
|
|
BroadcastConnectionLost();
|
|
});
|
|
});
|
|
TSharedPtr<FJsonObject> projectClosedSubscriptionResult = MakeShareable(new FJsonObject());
|
|
TSharedRef<FJsonObject> projectClosedOptions = MakeShareable(new FJsonObject());
|
|
uint64_t projectClosedSubscriptionID;
|
|
g_AkWaapiClient->Subscribe(ak::wwise::core::project::postClosed, projectClosedOptions, projectClosedCallback, projectClosedSubscriptionID, projectClosedSubscriptionResult);
|
|
#endif
|
|
}
|
|
|
|
/** Returns the path of the Wwise project as defined in AkSettings (WWise Plugin Settings). */
|
|
bool FAkWaapiClient::GetProjectPath(TSharedPtr<FJsonObject>& inOutJsonResult, FString& ProjectPath)
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
TArray<TSharedPtr<FJsonValue>> inFromItems;
|
|
inFromItems.Add(MakeShareable(new FJsonValueString("Project")));
|
|
const bool bSuccess = WAAPIGet(WAAPIGetFromOption::OF_TYPE, inFromItems, (AkInt64)WAAPIGetReturnOptionFlag::FILEPATH,
|
|
inOutJsonResult, WAAPIGetTransformOption::NONE, TArray<TSharedPtr<FJsonValue>>(), true);
|
|
if (bSuccess)
|
|
{
|
|
if (inOutJsonResult->HasField(FAkWaapiClient::WAAPIStrings::RETURN))
|
|
{
|
|
TArray<TSharedPtr<FJsonValue>> returnJson = inOutJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN);
|
|
if (returnJson.Num() > 0)
|
|
ProjectPath = returnJson[0]->AsObject()->GetStringField(FAkWaapiClient::WAAPIStrings::FILEPATH);
|
|
}
|
|
}
|
|
return bSuccess;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
// WAAPI can become temporarily unavailable from time to time, this should be taken into account in the design.
|
|
// Please note that you shouldn't use this kind of design unless you know your call is accurate. If you do this
|
|
// and the error is caused by an argument problem, you might end up with an infinite retry loop.
|
|
FString GetAndWaitForCurrentProject(float RetrySleepTimeSeconds)
|
|
{
|
|
FString ProjectPath;
|
|
static int RetryCount = 5;
|
|
while (g_AkWaapiClient->IsConnected())
|
|
{
|
|
TSharedPtr<FJsonObject> JsonResult = MakeShareable(new FJsonObject());
|
|
if (g_AkWaapiClient->GetProjectPath(JsonResult, ProjectPath))
|
|
break;
|
|
|
|
if (RetrySleepTimeSeconds > 0.0f)
|
|
{
|
|
if (--RetryCount == 0)
|
|
{
|
|
RetryCount = 5;
|
|
return {};
|
|
}
|
|
|
|
// Avoid flooding with requests while WAAPI is unavailable.
|
|
FPlatformProcess::Sleep(RetrySleepTimeSeconds);
|
|
}
|
|
}
|
|
#if PLATFORM_MAC
|
|
if(ProjectPath.StartsWith(TEXT("Y:")))
|
|
{
|
|
ProjectPath.ReplaceInline(TEXT("Y:"), *FPlatformMisc::GetEnvironmentVariable(TEXT("HOME")));
|
|
}
|
|
if(ProjectPath.StartsWith(TEXT("Z:")))
|
|
{
|
|
ProjectPath.ReplaceInline(TEXT("Z:"), *FPlatformMisc::GetEnvironmentVariable(TEXT("ROOT")));
|
|
}
|
|
ProjectPath.ReplaceInline(TEXT("\\"), TEXT("/"));
|
|
#endif
|
|
return ProjectPath;
|
|
}
|
|
|
|
/** Checks if the currently loaded Wwise project matches the project path set in AkSettings (Wwise plugin settings).
|
|
* NOTE: This function will block while Wwise has a modal window open. It should not be called on the Game thread.
|
|
*/
|
|
bool FAkWaapiClient::CheckProjectLoaded()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
if (g_AkWaapiClient == nullptr)
|
|
return false;
|
|
|
|
if (!g_AkWaapiClient->IsConnected())
|
|
{
|
|
g_AkWaapiClient->m_Impl->m_pConnectionHandler->ResetReconnectionDelay();
|
|
return false;
|
|
}
|
|
|
|
if (const UAkSettings* AkSettings = GetDefault<UAkSettings>())
|
|
{
|
|
auto ProjectPathString = AkUnrealHelper::GetWwiseProjectPath();
|
|
|
|
FString sCurrentlyLoadedProjectPath = GetAndWaitForCurrentProject(1.0f);
|
|
if (FPaths::IsSamePath(sCurrentlyLoadedProjectPath, ProjectPathString))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void FAkWaapiClient::BroadcastConnectionLost()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
m_Impl->bProjectLoaded = false;
|
|
OnConnectionLost.Broadcast();
|
|
#endif
|
|
}
|
|
|
|
bool FAkWaapiClient::IsConnected()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
if (UNLIKELY(!g_AkWaapiClient->m_Impl->m_Client))
|
|
{
|
|
return false;
|
|
}
|
|
return m_Impl->m_Client->IsConnected();
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
|
|
bool FAkWaapiClient::AttemptConnection()
|
|
{
|
|
bIsWrongProjectLoaded = false;
|
|
bool bConnected = false;
|
|
#if AK_SUPPORT_WAAPI
|
|
if (UNLIKELY(!g_AkWaapiClient->m_Impl->m_Client))
|
|
{
|
|
bConnected = false;
|
|
}
|
|
else if (const UAkSettingsPerUser* AkSettingsPerUser = GetDefault<UAkSettingsPerUser>())
|
|
{
|
|
bConnected = m_Impl->m_Client->Connect(TCHAR_TO_UTF8(*AkSettingsPerUser->WaapiIPAddress), AkSettingsPerUser->WaapiPort);
|
|
}
|
|
else
|
|
{
|
|
bConnected = m_Impl->m_Client->Connect(WAAPI_LOCAL_HOST_IP_STRING, WAAPI_PORT);
|
|
}
|
|
|
|
if (bConnected)
|
|
{
|
|
bool bProjectLoaded = CheckProjectLoaded();
|
|
if (!bProjectLoaded)
|
|
{
|
|
// We successfully connected, but the wrong project is open (or getting the project timed out). Disconnect.
|
|
// We will attemps reconnection later.
|
|
bIsWrongProjectLoaded = true;
|
|
m_Impl->m_Client->Disconnect();
|
|
bConnected = false;
|
|
}
|
|
m_Impl->bProjectLoaded = bProjectLoaded;
|
|
}
|
|
#endif
|
|
return bConnected;
|
|
}
|
|
|
|
bool FAkWaapiClient::Subscribe(const char* in_uri, const FString& in_options, WampEventCallback in_callback,
|
|
uint64& out_subscriptionId, FString& out_result, int in_iTimeoutMs /*= 500*/)
|
|
{
|
|
bool eResult = false;
|
|
#if AK_SUPPORT_WAAPI
|
|
std::string out_resultString("");
|
|
if (IsConnected())
|
|
{
|
|
if (!m_Impl->bIsConnectionClosing)
|
|
{
|
|
// Call for the AK WAAPI method using string params.
|
|
if (LIKELY(g_AkWaapiClient->m_Impl->m_Client))
|
|
{
|
|
FScopeLock Lock(&m_Impl->ClientSection);
|
|
eResult = m_Impl->m_Client->Subscribe(in_uri, TCHAR_TO_UTF8(*in_options), &WampEventCallbacks, out_subscriptionId, out_resultString, in_iTimeoutMs);
|
|
}
|
|
if (eResult)
|
|
{
|
|
m_Impl->m_wampEventCallbackMap.Add(out_subscriptionId, in_callback);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAkAudio, Log, TEXT("Subscription failed: %s"), *FString(UTF8_TO_TCHAR(out_resultString.c_str())));
|
|
}
|
|
out_result = FString(UTF8_TO_TCHAR(out_resultString.c_str()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_Impl && m_Impl->m_pConnectionHandler)
|
|
{
|
|
m_Impl->m_pConnectionHandler->ResetReconnectionDelay();
|
|
}
|
|
}
|
|
#endif
|
|
return eResult;
|
|
}
|
|
|
|
bool FAkWaapiClient::Subscribe(const char* in_uri, const TSharedRef<FJsonObject>& in_options, WampEventCallback in_callback,
|
|
uint64& out_subscriptionId, TSharedPtr<FJsonObject>& out_result, int in_iTimeoutMs /*= 500*/)
|
|
{
|
|
bool eResult = false;
|
|
#if AK_SUPPORT_WAAPI
|
|
FString in_optionsString = TEXT("");
|
|
|
|
// Retrieve the options data string from the Json object.
|
|
JsonObjectToString(in_options, in_optionsString);
|
|
|
|
FString out_resultString(TEXT(""));
|
|
// Call for the AK WAAPI method using string params.
|
|
eResult = Subscribe(in_uri, in_optionsString, in_callback, out_subscriptionId, out_resultString, in_iTimeoutMs);
|
|
|
|
if (!eResult)
|
|
{
|
|
// Deserialize a JSON object from the string.
|
|
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(out_resultString);
|
|
|
|
if ((!FJsonSerializer::Deserialize(Reader, out_result) || !out_result.IsValid()) && IsConnected())
|
|
{
|
|
UE_LOG(LogAkAudio, Log, TEXT("Subscribe: Output result -> unable to deserialize the Json object from the string : %s"), *out_resultString);
|
|
}
|
|
}
|
|
#endif
|
|
return eResult;
|
|
}
|
|
|
|
bool FAkWaapiClient::Unsubscribe(const uint64_t& in_subscriptionId, FString& out_result, int in_iTimeoutMs /*= 500*/, bool in_bSilenceLog /*= false*/)
|
|
{
|
|
bool eResult = false;
|
|
#if AK_SUPPORT_WAAPI
|
|
if (IsConnected())
|
|
{
|
|
if (!m_Impl->bIsConnectionClosing)
|
|
{
|
|
std::string out_resultString("");
|
|
// Call the AK WAAPI method.
|
|
if (LIKELY(g_AkWaapiClient->m_Impl->m_Client))
|
|
{
|
|
FScopeLock Lock(&m_Impl->ClientSection);
|
|
eResult = m_Impl->m_Client->Unsubscribe(in_subscriptionId, out_resultString, in_iTimeoutMs);
|
|
}
|
|
if (eResult)
|
|
{
|
|
if (m_Impl->m_wampEventCallbackMap.Contains(in_subscriptionId))
|
|
m_Impl->m_wampEventCallbackMap.Remove(in_subscriptionId);
|
|
}
|
|
else if (!in_bSilenceLog)
|
|
{
|
|
UE_LOG(LogAkAudio, Log, TEXT("Unsubscription failed: %s"), *FString(UTF8_TO_TCHAR(out_resultString.c_str())));
|
|
}
|
|
out_result = FString(UTF8_TO_TCHAR(out_resultString.c_str()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_Impl && m_Impl->m_pConnectionHandler)
|
|
{
|
|
m_Impl->m_pConnectionHandler->ResetReconnectionDelay();
|
|
}
|
|
}
|
|
#endif
|
|
return eResult;
|
|
}
|
|
|
|
bool FAkWaapiClient::Unsubscribe(const uint64_t& in_subscriptionId, TSharedPtr<FJsonObject>& out_result, int in_iTimeoutMs /*= 500*/, bool in_bSilenceLog /*= false*/)
|
|
{
|
|
bool eResult = false;
|
|
#if AK_SUPPORT_WAAPI
|
|
if (IsConnected())
|
|
{
|
|
FString out_resultString(TEXT(""));
|
|
// Call the AK WAAPI method.
|
|
eResult = Unsubscribe(in_subscriptionId, out_resultString, in_iTimeoutMs, in_bSilenceLog);
|
|
|
|
if (!eResult)
|
|
{
|
|
// Deserialize a JSON object from the string
|
|
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(out_resultString);
|
|
|
|
if ((!FJsonSerializer::Deserialize(Reader, out_result) || !out_result.IsValid()) && IsConnected())
|
|
{
|
|
UE_LOG(LogAkAudio, Log, TEXT("Unsubscribe: Output result -> unable to deserialize the Json object from the string : %s"), *out_resultString);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_Impl && m_Impl->m_pConnectionHandler)
|
|
{
|
|
m_Impl->m_pConnectionHandler->ResetReconnectionDelay();
|
|
}
|
|
}
|
|
#endif
|
|
return eResult;
|
|
}
|
|
|
|
bool FAkWaapiClient::RemoveWampEventCallback(const uint64_t in_subscriptionId)
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
if (m_Impl->m_wampEventCallbackMap.Contains(in_subscriptionId))
|
|
{
|
|
m_Impl->m_wampEventCallbackMap.Remove(in_subscriptionId);
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FAkWaapiClient::Call(const char* in_uri, const FString& in_args, const FString& in_options, FString& out_result, int in_iTimeoutMs /*= 500*/, bool silenceLog /* = false*/)
|
|
{
|
|
bool eResult = false;
|
|
#if AK_SUPPORT_WAAPI
|
|
if (IsConnected())
|
|
{
|
|
if (!m_Impl->bIsConnectionClosing)
|
|
{
|
|
std::string out_resultString("");
|
|
// Call the AK WAAPI method.
|
|
if (LIKELY(g_AkWaapiClient->m_Impl->m_Client))
|
|
{
|
|
FScopeLock Lock(&m_Impl->ClientSection);
|
|
eResult = m_Impl->m_Client->Call(in_uri, TCHAR_TO_UTF8(*in_args), TCHAR_TO_UTF8(*in_options), out_resultString, in_iTimeoutMs);
|
|
}
|
|
if (!eResult && !silenceLog)
|
|
{
|
|
UE_LOG(LogAkAudio, Log, TEXT("Call failed: %s"), *FString(UTF8_TO_TCHAR(out_resultString.c_str())));
|
|
}
|
|
out_result = FString(UTF8_TO_TCHAR(out_resultString.c_str()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_Impl && m_Impl->m_pConnectionHandler)
|
|
{
|
|
m_Impl->m_pConnectionHandler->ResetReconnectionDelay();
|
|
}
|
|
}
|
|
#endif
|
|
return eResult;
|
|
}
|
|
|
|
bool FAkWaapiClient::Call(const char* in_uri, const TSharedRef<FJsonObject>& in_args, const TSharedRef<FJsonObject>& in_options,
|
|
TSharedPtr<FJsonObject>& out_result, int in_iTimeoutMs /*= 500*/, bool silenceLog /*= false*/)
|
|
{
|
|
bool eResult = false;
|
|
#if AK_SUPPORT_WAAPI
|
|
FString in_argsString = TEXT("");
|
|
FString in_optionsString = TEXT("");
|
|
|
|
// Make sure the arguments are valid Json data.
|
|
JsonObjectToString(in_args, in_argsString);
|
|
|
|
// Make sure the options are valid Json data.
|
|
JsonObjectToString(in_options, in_optionsString);
|
|
|
|
FString out_resultString(TEXT(""));
|
|
// Call the AK WAAPI method.
|
|
eResult = Call(in_uri, in_argsString, in_optionsString, out_resultString, in_iTimeoutMs, silenceLog);
|
|
|
|
// Deserialize a JSON object from the string.
|
|
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(out_resultString);
|
|
|
|
if (!FJsonSerializer::Deserialize(Reader, out_result) || !out_result.IsValid())
|
|
{
|
|
if (!silenceLog && IsConnected())
|
|
{
|
|
UE_LOG(LogAkAudio, Log, TEXT("Output result -> unable to deserialize a JSON object from the string : %s"), *out_resultString);
|
|
}
|
|
}
|
|
#endif
|
|
return eResult;
|
|
}
|
|
|
|
bool FAkWaapiClient::Call(const char* inUri, const TArray<KeyValueArgs>& Values,
|
|
TSharedPtr<FJsonObject>& outJsonResult)
|
|
{
|
|
TSharedRef<FJsonObject> Args = MakeShareable(new FJsonObject());
|
|
|
|
for (const auto& Value : Values)
|
|
{
|
|
Args->SetStringField(Value.KeyArg, Value.ValueArg);
|
|
}
|
|
|
|
// Construct the options Json object;
|
|
TSharedRef<FJsonObject> Options = MakeShareable(new FJsonObject());
|
|
// Request infos/changes in Waapi Picker using WAAPI
|
|
if (Call(inUri, Args, Options, outJsonResult))
|
|
{
|
|
return true;
|
|
}
|
|
UE_LOG(LogAkAudio, Log, TEXT("Call %hs failed."), inUri);
|
|
return false;
|
|
}
|
|
|
|
FAkWaapiClient::FAkWaapiClient()
|
|
: m_Impl(new FAkWaapiClientImpl)
|
|
{
|
|
}
|
|
|
|
/** Sets in_outParentGUID to the object ID of a parent of object in_objectGUID of type in_strType. */
|
|
void FAkWaapiClient::GetParentOfType(FGuid in_objectGUID, FGuid& in_outParentGUID, FString in_strType)
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
if (g_AkWaapiClient == nullptr)
|
|
return;
|
|
|
|
/* Construct the relevant WAAPI json fields. */
|
|
AkInt64 returnFlags = (AkInt64)WAAPIGetReturnOptionFlag::ID |
|
|
(AkInt64)WAAPIGetReturnOptionFlag::TYPE;
|
|
|
|
TArray<TSharedPtr<FJsonValue>> fromID;
|
|
fromID.Add(MakeShareable(new FJsonValueString(in_objectGUID.ToString(EGuidFormats::DigitsWithHyphensInBraces))));
|
|
|
|
TSharedPtr<FJsonObject> outJsonResult;
|
|
if (!WAAPIGet(WAAPIGetFromOption::ID, fromID, returnFlags, outJsonResult))
|
|
return;
|
|
|
|
if (!outJsonResult->HasField(WAAPIStrings::RETURN))
|
|
return;
|
|
|
|
TArray<TSharedPtr<FJsonValue>> returnJson = outJsonResult->GetArrayField(WAAPIStrings::RETURN);
|
|
if (returnJson.Num() <= 0)
|
|
return;
|
|
|
|
FString objectType;
|
|
{
|
|
TSharedPtr<FJsonObject> typeObject = returnJson[0]->AsObject();
|
|
if (typeObject->HasField(WAAPIStrings::TYPE))
|
|
objectType = typeObject->GetStringField(WAAPIStrings::TYPE);
|
|
}
|
|
in_outParentGUID = in_objectGUID;
|
|
|
|
if (objectType.Equals(in_strType, ESearchCase::IgnoreCase))
|
|
return;
|
|
|
|
TSharedPtr<FJsonObject> select = MakeShareable(new FJsonObject());
|
|
TArray<TSharedPtr<FJsonValue>> selectJsonArray;
|
|
selectJsonArray.Add(MakeShareable(new FJsonValueString(WAAPIStrings::PARENT)));
|
|
select->SetArrayField(WAAPIStrings::SELECT, selectJsonArray);
|
|
TArray<TSharedPtr<FJsonValue>> transform;
|
|
transform.Add(MakeShareable(new FJsonValueObject(select)));
|
|
|
|
while (!objectType.Equals(in_strType, ESearchCase::IgnoreCase))
|
|
{
|
|
fromID.Empty();
|
|
fromID.Add(MakeShareable(new FJsonValueString(in_outParentGUID.ToString(EGuidFormats::DigitsWithHyphensInBraces))));
|
|
outJsonResult = MakeShareable(new FJsonObject());
|
|
if (!WAAPIGet(WAAPIGetFromOption::ID, fromID, returnFlags, outJsonResult, WAAPIGetTransformOption::SELECT, selectJsonArray))
|
|
break;
|
|
|
|
if (!outJsonResult->HasField(WAAPIStrings::RETURN))
|
|
continue;
|
|
|
|
TArray<TSharedPtr<FJsonValue>> aReturnJson = outJsonResult->GetArrayField(WAAPIStrings::RETURN);
|
|
TSharedPtr<FJsonObject> returnObject = aReturnJson[0]->AsObject();
|
|
if (returnObject->HasField(WAAPIStrings::TYPE))
|
|
objectType = returnObject->GetStringField(WAAPIStrings::TYPE);
|
|
if (returnObject->HasField(WAAPIStrings::ID))
|
|
FGuid::Parse(returnObject->GetStringField(WAAPIStrings::ID), in_outParentGUID);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool FAkWaapiClient::IsProjectDirty()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
/* Ensure that Initialize() has been called! */
|
|
AKASSERT(g_AkWaapiClient != nullptr);
|
|
|
|
TArray<TSharedPtr<FJsonValue>> inFromItems;
|
|
AkInt64 iReturnOptions = (uint64_t)WAAPIGetReturnOptionFlag::WORKUNIT_IS_DIRTY;
|
|
TSharedPtr<FJsonObject> pJsonResult = MakeShareable(new FJsonObject());
|
|
inFromItems.Add(MakeShareable(new FJsonValueString("WorkUnit")));
|
|
WAAPIGet(WAAPIGetFromOption::OF_TYPE, inFromItems, iReturnOptions, pJsonResult);
|
|
if (pJsonResult->HasField(WAAPIStrings::RETURN))
|
|
{
|
|
TArray<TSharedPtr<FJsonValue>> returnJson = pJsonResult->GetArrayField(WAAPIStrings::RETURN);
|
|
FString workunitDirty = GetReturnOptionString(WAAPIGetReturnOptionFlag::WORKUNIT_IS_DIRTY);
|
|
for (auto json : returnJson)
|
|
{
|
|
if (json->AsObject()->HasField(workunitDirty) && json->AsObject()->GetBoolField(workunitDirty))
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* WAAPI Structures
|
|
*/
|
|
|
|
FString FAkWaapiClient::GetFromOptionString(WAAPIGetFromOption from)
|
|
{
|
|
switch (from)
|
|
{
|
|
case WAAPIGetFromOption::ID: return "id";
|
|
case WAAPIGetFromOption::SEARCH: return "search";
|
|
case WAAPIGetFromOption::PATH: return "path";
|
|
case WAAPIGetFromOption::OF_TYPE: return "ofType";
|
|
case WAAPIGetFromOption::QUERY: return "query";
|
|
default: AKASSERT(false && "From option unhandled"); return "";
|
|
}
|
|
}
|
|
|
|
FString FAkWaapiClient::GetTransformOptionString(WAAPIGetTransformOption transform)
|
|
{
|
|
switch (transform)
|
|
{
|
|
case WAAPIGetTransformOption::SELECT: return "select";
|
|
case WAAPIGetTransformOption::RANGE: return "range";
|
|
case WAAPIGetTransformOption::WHERE: return "where";
|
|
case WAAPIGetTransformOption::NONE: return "";
|
|
default: AKASSERT(false && "Transform option unhandled"); return "";
|
|
}
|
|
}
|
|
|
|
FAkWaapiClient::WAAPIGetReturnOptionFlag FAkWaapiClient::GetReturnOptionFlagValue(int in_iFlagIndex)
|
|
{
|
|
return (WAAPIGetReturnOptionFlag)(AkInt64)pow(2, in_iFlagIndex);
|
|
}
|
|
|
|
FString FAkWaapiClient::GetReturnOptionString(WAAPIGetReturnOptionFlag returnOption)
|
|
{
|
|
switch (returnOption)
|
|
{
|
|
case WAAPIGetReturnOptionFlag::ID: return "id";
|
|
case WAAPIGetReturnOptionFlag::NAME: return "name";
|
|
case WAAPIGetReturnOptionFlag::NOTES: return "notes";
|
|
case WAAPIGetReturnOptionFlag::TYPE: return "type";
|
|
case WAAPIGetReturnOptionFlag::PATH: return "path";
|
|
case WAAPIGetReturnOptionFlag::PARENT: return "parent";
|
|
case WAAPIGetReturnOptionFlag::OWNER: return "owner";
|
|
case WAAPIGetReturnOptionFlag::IS_PLAYABLE: return "isPlayable";
|
|
case WAAPIGetReturnOptionFlag::SHORT_ID: return "shortId";
|
|
case WAAPIGetReturnOptionFlag::CATEGORY: return "category";
|
|
case WAAPIGetReturnOptionFlag::FILEPATH: return "filePath";
|
|
case WAAPIGetReturnOptionFlag::WORKUNIT: return "workunit";
|
|
case WAAPIGetReturnOptionFlag::CHILDREN_COUNT: return "childrenCount";
|
|
case WAAPIGetReturnOptionFlag::MUSIC_TRANSITION_ROOT: return "music:transitionRoot";
|
|
case WAAPIGetReturnOptionFlag::MUSIC_PLAYLIST_ROOT: return "music:playlistRoot";
|
|
case WAAPIGetReturnOptionFlag::SOUND_ORIGINAL_WAV_FILE_PATH: return "sound:originalWavFilePath";
|
|
case WAAPIGetReturnOptionFlag::SOUND_CONVERTED_WEM_FILE_PATH: return "sound:convertedWemFilePath";
|
|
case WAAPIGetReturnOptionFlag::SOUNDBANK_BANK_FILE_PATH: return "soundbank:bnkFilePath";
|
|
case WAAPIGetReturnOptionFlag::AUDIO_SOURCE_PLAYBACK_DURATION: return "audioSource:playbackDuration";
|
|
case WAAPIGetReturnOptionFlag::AUDIO_SOURCE_MAX_DURATION_SOURCE: return "audioSource:maxDurationSource";
|
|
case WAAPIGetReturnOptionFlag::AUDIO_SOURCE_TRIM_VALUES: return "audioSource:trimValues";
|
|
case WAAPIGetReturnOptionFlag::WORKUNIT_IS_DEFAULT: return "workunit:isDefault";
|
|
case WAAPIGetReturnOptionFlag::WORKUNIT_TYPE: return "workunit:type";
|
|
case WAAPIGetReturnOptionFlag::WORKUNIT_IS_DIRTY: return "workunit:isDirty";
|
|
default: AKASSERT(false && "Return option unhandled"); return "";
|
|
}
|
|
}
|
|
/**
|
|
* JSon Helpers
|
|
*/
|
|
TSharedRef<FJsonObject> FAkWaapiClient::CreateWAAPIGetArgumentJson(WAAPIGetFromOption in_FromOption, TArray<TSharedPtr<FJsonValue>> in_FromItems,
|
|
WAAPIGetTransformOption in_TransformOption /*= WAAPIGetTransformOption::NONE*/,
|
|
TArray<TSharedPtr<FJsonValue>> in_TransformItems /*= TArray<TSharedPtr<FJsonValue>>()*/)
|
|
{
|
|
TSharedRef<FJsonObject> args = MakeShareable(new FJsonObject());
|
|
TSharedPtr<FJsonObject> from = MakeShareable(new FJsonObject());
|
|
from->SetArrayField(GetFromOptionString(in_FromOption), in_FromItems);
|
|
args->SetObjectField(FAkWaapiClient::WAAPIStrings::FROM, from);
|
|
if (in_TransformOption != WAAPIGetTransformOption::NONE && in_TransformItems.Num() > 0)
|
|
{
|
|
TArray<TSharedPtr<FJsonValue>> transformArgArray;
|
|
TSharedPtr<FJsonObject> transformObjectArg = MakeShareable(new FJsonObject());
|
|
transformObjectArg->SetArrayField(GetTransformOptionString(in_TransformOption), in_TransformItems);
|
|
transformArgArray.Add(MakeShareable(new FJsonValueObject(transformObjectArg)));
|
|
args->SetArrayField(FAkWaapiClient::WAAPIStrings::TRANSFORM, transformArgArray);
|
|
}
|
|
return args;
|
|
}
|
|
|
|
TSharedRef<FJsonObject> FAkWaapiClient::CreateWAAPIGetReturnOptionsJson(AkInt64 ReturnOptions)
|
|
{
|
|
TSharedRef<FJsonObject> options = MakeShareable(new FJsonObject());
|
|
TArray<TSharedPtr<FJsonValue>> StructJsonArray;
|
|
for (int bitIndex = 0; bitIndex < (int)WAAPIGetReturnOptionFlag::NUM_FLAGS; ++bitIndex)
|
|
{
|
|
WAAPIGetReturnOptionFlag returnOption = GetReturnOptionFlagValue(bitIndex);
|
|
if ((ReturnOptions & (AkInt64)returnOption) != 0)
|
|
{
|
|
StructJsonArray.Add(MakeShareable(new FJsonValueString(GetReturnOptionString(returnOption))));
|
|
}
|
|
}
|
|
|
|
options->SetArrayField(FAkWaapiClient::WAAPIStrings::RETURN, StructJsonArray);
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* WAAPI Helpers
|
|
*/
|
|
|
|
bool FAkWaapiClient::WAAPIGet(WAAPIGetFromOption inFromField,
|
|
TArray<TSharedPtr<FJsonValue>> inFromItems,
|
|
AkInt64 inReturnOptionsFlags,
|
|
TSharedPtr<FJsonObject>& outJsonResult,
|
|
WAAPIGetTransformOption inTransformField /*= WAAPIGetTransformOption::NONE*/,
|
|
TArray<TSharedPtr<FJsonValue>> inTransformItems /*= TArray<TSharedPtr<FJsonValue>>()*/,
|
|
bool in_bSilenceLog /*= false*/)
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
TSharedRef<FJsonObject> getArgsJson = CreateWAAPIGetArgumentJson(inFromField, inFromItems, inTransformField, inTransformItems);
|
|
TSharedRef<FJsonObject> returnOptionsJson = CreateWAAPIGetReturnOptionsJson(inReturnOptionsFlags);
|
|
|
|
if (g_AkWaapiClient != nullptr && g_AkWaapiClient->IsConnected())
|
|
{
|
|
if (g_AkWaapiClient->Call(ak::wwise::core::object::get, getArgsJson, returnOptionsJson, outJsonResult, 500, in_bSilenceLog))
|
|
return true;
|
|
else if (!in_bSilenceLog)
|
|
UE_LOG(LogAkAudio, Log, TEXT("Call to ak.wwise.core.object.get Failed"));
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool FAkWaapiClient::GetGUIDForObjectOfTypeWithName(FGuid& io_GUID, const FString& in_sTypeName, const FString& in_sName)
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
TArray<TSharedPtr<FJsonValue>> nameArray;
|
|
nameArray.Add(MakeShareable(new FJsonValueString(in_sName)));
|
|
TSharedPtr<FJsonObject> outJsonResult = MakeShareable(new FJsonObject());
|
|
AkInt64 returnOptionFlags = (AkInt64)WAAPIGetReturnOptionFlag::ID | (AkInt64)WAAPIGetReturnOptionFlag::NAME | (AkInt64)WAAPIGetReturnOptionFlag::TYPE;
|
|
if (WAAPIGet(WAAPIGetFromOption::SEARCH, nameArray, returnOptionFlags, outJsonResult))
|
|
{
|
|
if (outJsonResult->HasField(WAAPIStrings::RETURN))
|
|
{
|
|
TArray<TSharedPtr<FJsonValue>> returnJson = outJsonResult->GetArrayField(WAAPIStrings::RETURN);
|
|
for (auto json : returnJson)
|
|
{
|
|
auto jsonObj = json->AsObject();
|
|
auto name = jsonObj->GetStringField(WAAPIStrings::NAME);
|
|
auto typeName = jsonObj->GetStringField(WAAPIStrings::TYPE);
|
|
if (name == in_sName && typeName.Equals(in_sTypeName, ESearchCase::IgnoreCase))
|
|
{
|
|
auto iD = jsonObj->GetStringField(WAAPIStrings::ID);
|
|
FGuid::Parse(iD, io_GUID);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void FAkWaapiClient::SaveProject()
|
|
{
|
|
#if AK_SUPPORT_WAAPI
|
|
TSharedRef<FJsonObject> argsJson = MakeShareable(new FJsonObject());
|
|
TSharedRef<FJsonObject> optionsJson = MakeShareable(new FJsonObject());
|
|
TSharedPtr<FJsonObject> outputJson = MakeShareable(new FJsonObject());
|
|
if (g_AkWaapiClient->IsConnected())
|
|
{
|
|
g_AkWaapiClient->Call(ak::wwise::core::project::save, argsJson, optionsJson, outputJson);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
const FString FAkWaapiClient::WAAPIStrings::BACK_SLASH = TEXT("\\");
|
|
const FString FAkWaapiClient::WAAPIStrings::ID = TEXT("id");
|
|
const FString FAkWaapiClient::WAAPIStrings::RETURN = TEXT("return");
|
|
const FString FAkWaapiClient::WAAPIStrings::PATH = TEXT("path");
|
|
const FString FAkWaapiClient::WAAPIStrings::FILEPATH = TEXT("filePath");
|
|
const FString FAkWaapiClient::WAAPIStrings::FROM = TEXT("from");
|
|
const FString FAkWaapiClient::WAAPIStrings::NAME = TEXT("name");
|
|
const FString FAkWaapiClient::WAAPIStrings::TYPE = TEXT("type");
|
|
const FString FAkWaapiClient::WAAPIStrings::CHILDREN = TEXT("children");
|
|
const FString FAkWaapiClient::WAAPIStrings::CHILDREN_COUNT = TEXT("childrenCount");
|
|
const FString FAkWaapiClient::WAAPIStrings::ANCESTORS = TEXT("ancestors");
|
|
const FString FAkWaapiClient::WAAPIStrings::DESCENDANTS = TEXT("descendants");
|
|
const FString FAkWaapiClient::WAAPIStrings::WOKUNIT_TYPE = TEXT("workunit:type");
|
|
const FString FAkWaapiClient::WAAPIStrings::FOLDER = TEXT("Folder");
|
|
const FString FAkWaapiClient::WAAPIStrings::PHYSICAL_FOLDER = TEXT("PhysicalFolder");
|
|
const FString FAkWaapiClient::WAAPIStrings::SEARCH = TEXT("search");
|
|
const FString FAkWaapiClient::WAAPIStrings::PARENT = TEXT("parent");
|
|
const FString FAkWaapiClient::WAAPIStrings::SELECT = TEXT("select");
|
|
const FString FAkWaapiClient::WAAPIStrings::TRANSFORM = TEXT("transform");
|
|
const FString FAkWaapiClient::WAAPIStrings::OBJECT = TEXT("object");
|
|
const FString FAkWaapiClient::WAAPIStrings::OBJECTS = TEXT("objects");
|
|
const FString FAkWaapiClient::WAAPIStrings::VALUE = TEXT("value");
|
|
const FString FAkWaapiClient::WAAPIStrings::COMMAND = TEXT("command");
|
|
const FString FAkWaapiClient::WAAPIStrings::TRANSPORT = TEXT("transport");
|
|
const FString FAkWaapiClient::WAAPIStrings::ACTION = TEXT("action");
|
|
const FString FAkWaapiClient::WAAPIStrings::PLAY = TEXT("play");
|
|
const FString FAkWaapiClient::WAAPIStrings::STOP = TEXT("stop");
|
|
const FString FAkWaapiClient::WAAPIStrings::STOPPED = TEXT("stopped");
|
|
const FString FAkWaapiClient::WAAPIStrings::DISPLAY_NAME = TEXT("displayName");
|
|
const FString FAkWaapiClient::WAAPIStrings::DELETE_ITEMS = TEXT("Delete Items");
|
|
const FString FAkWaapiClient::WAAPIStrings::DRAG_DROP_ITEMS = TEXT("Drag Drop Items");
|
|
const FString FAkWaapiClient::WAAPIStrings::UNDO = TEXT("Undo");
|
|
const FString FAkWaapiClient::WAAPIStrings::REDO = TEXT("Redo");
|
|
const FString FAkWaapiClient::WAAPIStrings::STATE = TEXT("state");
|
|
const FString FAkWaapiClient::WAAPIStrings::OF_TYPE = TEXT("ofType");
|
|
const FString FAkWaapiClient::WAAPIStrings::PROJECT = TEXT("Project");
|
|
const FString FAkWaapiClient::WAAPIStrings::PROPERTY = TEXT("property");
|
|
const FString FAkWaapiClient::WAAPIStrings::VOLUME = TEXT("Volume");
|
|
const FString FAkWaapiClient::WAAPIStrings::FIND_IN_PROJECT_EXPLORER = TEXT("FindInProjectExplorerSelectionChannel1");
|
|
const FString FAkWaapiClient::WAAPIStrings::TRIMMED_DURATION = TEXT("trimmedDuration");
|
|
|
|
const FString FAkWaapiClient::WwiseTypeStrings::SOUND = TEXT("Sound");
|
|
const FString FAkWaapiClient::WwiseTypeStrings::WORKUNIT = TEXT("WorkUnit");
|
|
|
|
const FString FAkWaapiClient::AudioPeaksStrings::Args::OBJECT = TEXT("object");
|
|
const FString FAkWaapiClient::AudioPeaksStrings::Args::NUM_PEAKS = TEXT("numPeaks");
|
|
const FString FAkWaapiClient::AudioPeaksStrings::Args::TIME_FROM = TEXT("timeFrom");
|
|
const FString FAkWaapiClient::AudioPeaksStrings::Args::TIME_TO = TEXT("timeTo");
|
|
const FString FAkWaapiClient::AudioPeaksStrings::Args::CROSS_CHANNEL_PEAKS = TEXT("getCrossChannelPeaks");
|
|
const FString FAkWaapiClient::AudioPeaksStrings::Results::PEAKS_BINARY = TEXT("peaksBinaryStrings");
|
|
const FString FAkWaapiClient::AudioPeaksStrings::Results::MAX_ABS_VALUE = TEXT("maxAbsValue");
|
|
const FString FAkWaapiClient::AudioPeaksStrings::Results::PEAKS_ARRAY_LENGTH = TEXT("peaksArrayLength");
|
|
const FString FAkWaapiClient::AudioPeaksStrings::Results::PEAKS_DATA_SIZE = TEXT("peaksDataSize");
|
|
|
|
const FString FAkWaapiClient::PropertyChangedStrings::RequiredOptions::OBJECT = TEXT("object");
|
|
const FString FAkWaapiClient::PropertyChangedStrings::RequiredOptions::PROPERTY = TEXT("property");
|
|
const FString FAkWaapiClient::PropertyChangedStrings::OptionalOptions::RETURN = TEXT("return");
|
|
const FString FAkWaapiClient::PropertyChangedStrings::OptionalOptions::PLATFORM = TEXT("platform");
|
|
|
|
const FString FAkWaapiClient::AudioSourceProperties::TRIM_END = TEXT("TrimEnd");
|
|
const FString FAkWaapiClient::AudioSourceProperties::TRIM_BEGIN = TEXT("TrimBegin");
|
|
|
|
const FString FAkWaapiClient::PlaybackDurationStrings::MIN = TEXT("playbackDurationMin");
|
|
const FString FAkWaapiClient::PlaybackDurationStrings::MAX = TEXT("playbackDurationMax");
|
|
const FString FAkWaapiClient::PlaybackDurationStrings::TYPE = TEXT("playbackDurationType");
|
|
|
|
const FString FAkWaapiClient::TrimValuesStrings::TRIM_BEGIN = TEXT("trimBegin");
|
|
const FString FAkWaapiClient::TrimValuesStrings::TRIM_END = TEXT("trimEnd");
|
|
// end
|