/******************************************************************************* 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. *******************************************************************************/ /*============================================================================= AkAudioDevice.cpp: Audiokinetic Audio interface object. =============================================================================*/ #define AK_ENABLE_ROOMS #define AK_ENABLE_PORTALS #include "AkAudioDevice.h" #include "AkAcousticPortal.h" #include "AkAudioEvent.h" #include "AkAudioModule.h" #include "Wwise/WwiseFileHandlerModule.h" #include "Wwise/WwiseIOHook.h" #include "Wwise/WwiseResourceLoader.h" #include "Wwise/WwiseResourceLoaderImpl.h" #include "Wwise/API/WAAPI.h" #include "Wwise/API/WwiseMonitorAPI.h" #include "Wwise/API/WwiseSoundEngineAPI.h" #include "Wwise/API/WwiseSpatialAudioAPI.h" #include "Wwise/API/WwiseStreamMgrAPI.h" #include "Wwise/Stats/Global.h" #include "WwiseInitBankLoader/WwiseInitBankLoader.h" #include "AkCallbackInfoPool.h" #include "AkComponent.h" #include "AkComponentCallbackManager.h" #include "AkComponentHelpers.h" #include "AkGameObject.h" #include "AkLateReverbComponent.h" #include "AkRoomComponent.h" #include "AkRtpc.h" #include "AkSettings.h" #include "AkSpotReflector.h" #include "AkStateValue.h" #include "AkSwitchValue.h" #include "AkTrigger.h" #include "WwiseUnrealDefines.h" #include "AkWaapiClient.h" #include "AkWaapiUtils.h" #include "EngineUtils.h" #include "Async/Async.h" #include "Async/TaskGraphInterfaces.h" #include "Camera/PlayerCameraManager.h" #include "Components/BrushComponent.h" #include "Engine/GameEngine.h" #include "GameFramework/PlayerController.h" #include "GameFramework/WorldSettings.h" #include "InitializationSettings/AkInitializationSettings.h" #include "Internationalization/Culture.h" #include "Internationalization/Internationalization.h" #include "Misc/App.h" #include "Misc/ScopeLock.h" #include "Runtime/Launch/Resources/Version.h" #include "UObject/Object.h" #include "UObject/UObjectGlobals.h" #include "UObject/UObjectIterator.h" #include "Wwise/WwiseExternalSourceManager.h" #if WITH_EDITOR #include "Editor.h" #include "EditorSupportDelegates.h" #include "LevelEditor.h" #include "SEditorViewport.h" #include "UnrealEdMisc.h" #ifndef AK_OPTIMIZED #include "AkSettingsPerUser.h" #endif #endif #if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) #include "Wwise/WwiseProjectDatabase.h" #endif #include /*------------------------------------------------------------------------------------ Statics and Globals ------------------------------------------------------------------------------------*/ bool FAkAudioDevice::m_bSoundEngineInitialized = false; bool FAkAudioDevice::m_EngineExiting = false; TMap> FAkAudioDevice::EventToPlayingIDMap; TMap FAkAudioDevice::PlayingIDToAudioContextMap; TMap FAkAudioDevice::OnSwitchValueLoadedMap; TArray> FAkAudioDevice::AudioObjectsToLoadAfterInitialization; FCriticalSection FAkAudioDevice::EventToPlayingIDMapCriticalSection; /*------------------------------------------------------------------------------------ Defines ------------------------------------------------------------------------------------*/ static constexpr auto InvariantLCID = 0x7F; /*------------------------------------------------------------------------------------ Helpers ------------------------------------------------------------------------------------*/ namespace FAkAudioDevice_Helpers { #if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) static TMap ComponentNameMap; #endif AKRESULT RegisterGameObject(AkGameObjectID in_gameObjId, const FString& Name) { AKRESULT Result; auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; #ifdef AK_OPTIMIZED Result = SoundEngine->RegisterGameObj(in_gameObjId); #else if (Name.Len() > 0) { Result = SoundEngine->RegisterGameObj(in_gameObjId, TCHAR_TO_ANSI(*Name)); } else { Result = SoundEngine->RegisterGameObj(in_gameObjId); } #endif #if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) if (Result == AK_Success) { ComponentNameMap.Add(in_gameObjId, FName(Name)); } #endif UE_CLOG(LIKELY(Result == AK_Success), LogAkAudio, VeryVerbose, TEXT("Registered Object ID %" PRIu64 " (%s)"), in_gameObjId, *Name); UE_CLOG(UNLIKELY(Result != AK_Success), LogAkAudio, Warning, TEXT("Error registering Object ID %" PRIu64 " (%s): (%" PRIu32 ") %s"), in_gameObjId, *Name, Result, WwiseUnrealHelper::GetResultString(Result)); return Result; } typedef TMap FDelegateLocationMap; FDelegateLocationMap DelegateLocationMap; void GlobalCallback(AK::IAkGlobalPluginContext* Context, AkGlobalCallbackLocation Location, void* Cookie) { const FAkAudioDeviceDelegates::FOnAkGlobalCallback* Delegate = DelegateLocationMap.Find(Location); if (Delegate && Delegate->IsBound()) { Delegate->Broadcast(Context, Location); } } void UnregisterGlobalCallbackDelegate(FAkAudioDeviceDelegates::FOnAkGlobalCallback* Delegate, FDelegateHandle Handle, AkGlobalCallbackLocation Location) { if (!Delegate) return; Delegate->Remove(Handle); if (Delegate->IsBound()) return; auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; SoundEngine->UnregisterGlobalCallback(GlobalCallback, Location); } void UnregisterAllGlobalCallbacks() { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (LIKELY(SoundEngine)) { for (auto DelegateLocationPair : FAkAudioDevice_Helpers::DelegateLocationMap) { auto Location = DelegateLocationPair.Key; SoundEngine->UnregisterGlobalCallback(GlobalCallback, Location); } } FAkAudioDevice_Helpers::DelegateLocationMap.Empty(); } } #if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) static bool GetInfoErrorMessageTranslatorFunction(IWwiseSoundEngineAPI::TagInformationBridge::Type* in_pTagList, AkUInt32 in_uCount, AkUInt32& out_uTranslated) { FString Name; FWwiseDataStructureScopeLock DB(*FWwiseProjectDatabase::Get()); for (AkUInt32 i = 0; i < in_uCount && out_uTranslated != in_uCount; i++) { auto& Tag = in_pTagList[i]; if (Tag.m_infoIsParsed) continue; const AkUInt32 ID = FPlatformMemory::ReadUnaligned((AkUInt8*)Tag.m_args); const auto AssetInfo = FWwiseObjectInfo(ID); switch (*Tag.m_pTag) { case 'b': //Bank ID { const auto RefBank = DB.GetSoundBank(AssetInfo); if (LIKELY(RefBank.IsValid())) { Name = RefBank.SoundBankShortName().ToString(); } break; } case 'g': //The only tag in 64 bits { const AkUInt64 GoId = FPlatformMemory::ReadUnaligned((AkUInt8*)Tag.m_args); const auto* NamePtr = FAkAudioDevice_Helpers::ComponentNameMap.Find(GoId); if (LIKELY(NamePtr)) { Name = NamePtr->ToString(); } break; } case 'm': //Media ID { const auto RefMedia = DB.GetMediaFile(AssetInfo); if (LIKELY(RefMedia.IsValid())) { Name = RefMedia.MediaShortName().ToString(); } break; } case 'p': //Plugin { const auto RefPlugin = DB.GetCustomPlugin(AssetInfo); if (LIKELY(RefPlugin.IsValid())) { Name = RefPlugin.CustomPluginName().ToString(); } break; } case 's': //SwitchStates ID { const auto RefSwitchGroup = DB.GetSwitchGroup(AssetInfo); if (RefSwitchGroup.IsValid()) { Name = RefSwitchGroup.SwitchGroupName().ToString(); break; } const auto RefStateGroup = DB.GetStateGroup(AssetInfo); if (RefStateGroup.IsValid()) { Name = RefStateGroup.StateGroupName().ToString(); } break; } case 'w': //WwiseObject ID { //$w is generic, it can mean a lot of unrelated types. const TCHAR* FoundType = TEXT(""); FString FoundName; const auto EventInfo = FWwiseEventInfo(ID); const auto RefEvents = DB.GetEvent(EventInfo); if (RefEvents.Num() > 0) { FoundType = TEXT("Event"); FoundName = RefEvents.Array()[0].EventName().ToString(); } const auto RefGameParameter = DB.GetGameParameter(AssetInfo); if (RefGameParameter.IsValid()) { const auto NewName = RefGameParameter.GameParameterName().ToString(); if (UNLIKELY(!FoundName.IsEmpty() && NewName != FoundName)) { UE_LOG(LogAkAudio, Warning, TEXT("Found two different names for the same object ID %" PRIu32 ": %s %s and GameParameter %s. Ignoring."), FoundType, *FoundName, *NewName); continue; } FoundType = TEXT("GameParameter"); FoundName = NewName; } const auto RefShareSet = DB.GetPluginShareSet(AssetInfo); if (RefShareSet.IsValid()) { const auto NewName = RefShareSet.PluginShareSetName().ToString(); if (UNLIKELY(!FoundName.IsEmpty() && NewName != FoundName)) { UE_LOG(LogAkAudio, Warning, TEXT("Found two different names for the same object ID %" PRIu32 ": %s %s and ShareSet %s. Ignoring."), FoundType, *FoundName, *NewName); continue; } FoundType = TEXT("ShareSet"); FoundName = NewName; } const auto RefBus = DB.GetBus(AssetInfo); if (RefBus.IsValid()) { const auto NewName = RefBus.BusName().ToString(); if (UNLIKELY(!FoundName.IsEmpty() && NewName != FoundName)) { UE_LOG(LogAkAudio, Warning, TEXT("Found two different names for the same object ID %" PRIu32 ": %s %s and Bus %s. Ignoring."), FoundType, *FoundName, *NewName); continue; } FoundType = TEXT("Bus"); FoundName = NewName; } const auto RefAuxBus = DB.GetAuxBus(AssetInfo); if (RefAuxBus.IsValid()) { const auto NewName = RefAuxBus.AuxBusName().ToString(); if (UNLIKELY(!FoundName.IsEmpty() && NewName != FoundName)) { UE_LOG(LogAkAudio, Warning, TEXT("Found two different names for the same object ID %" PRIu32 ": %s %s and AuxBus %s. Ignoring."), FoundType, *FoundName, *NewName); continue; } FoundType = TEXT("AuxBus"); FoundName = NewName; } // Can be other things, such as Sound. if (!FoundName.IsEmpty()) { Name = FoundName; break; } else { continue; } } default: continue; //Not supported, ignore. } if (!Name.IsEmpty()) { AKPLATFORM::SafeStrCpy(Tag.m_parsedInfo, TCHAR_TO_AK(*Name), AK_TRANSLATOR_MAX_NAME_SIZE); Tag.m_len = Name.Len(); Tag.m_infoIsParsed = true; out_uTranslated++; } } return out_uTranslated == in_uCount; } #endif /*------------------------------------------------------------------------------------ Implementation ------------------------------------------------------------------------------------*/ FDelegateHandle FAkAudioDevice::RegisterGlobalCallback(FAkAudioDeviceDelegates::FOnAkGlobalCallback::FDelegate Callback, AkGlobalCallbackLocation Location) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return {}; auto& Delegate = FAkAudioDevice_Helpers::DelegateLocationMap.FindOrAdd(Location); FDelegateHandle Handle = Delegate.Add(Callback); auto result = SoundEngine->RegisterGlobalCallback(FAkAudioDevice_Helpers::GlobalCallback, Location); if (result != AK_Success) { FAkAudioDevice_Helpers::UnregisterGlobalCallbackDelegate(&Delegate, Handle, Location); Handle.Reset(); } return Handle; } void FAkAudioDevice::UnregisterGlobalCallback(FDelegateHandle Handle, AkGlobalCallbackLocation Location) { const auto& Delegate = FAkAudioDevice_Helpers::DelegateLocationMap.Find(Location); FAkAudioDevice_Helpers::UnregisterGlobalCallbackDelegate(Delegate, Handle, Location); } AKRESULT FAkAudioDevice::RegisterOutputDeviceMeteringCallback(AkOutputDeviceID OutputID, AkOutputDeviceMeteringCallbackFunc Callback, AkMeteringFlags MeteringFlags, void* Cookie) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->RegisterOutputDeviceMeteringCallback(OutputID, Callback, MeteringFlags, Cookie); } AKRESULT FAkAudioDevice::UnregisterOutputDeviceMeteringCallback(AkOutputDeviceID OutputID) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->RegisterOutputDeviceMeteringCallback(OutputID, nullptr, AK_NoMetering, nullptr); } #if WITH_EDITORONLY_DATA UAkComponent* FAkAudioDevice::CreateListener(UWorld* World, FEditorViewportClient* ViewportClient) { if (!IsRunningGame()) { FString ComponentName = TEXT("AkListener_") + World->GetName(); if (ViewportClient) { ComponentName = TEXT("AkListener_") + FString::FromInt(ViewportClient->ViewIndex) + World->GetName(); } UAkComponent* Listener = NewObject(World->GetWorldSettings(), FName(*ComponentName), RF_Transient); if (Listener != nullptr) { Listener->MarkAsEditorOnlySubobject(); Listener->RegisterComponentWithWorld(World); AddDefaultListener(Listener); } return Listener; } else { return nullptr; } } FTransform FAkAudioDevice::GetEditorListenerPosition(int32 ViewIndex) const { if (ViewIndex < ListenerTransforms.Num()) { return ListenerTransforms[ViewIndex]; } return FTransform(); } #endif bool FAkAudioDevice::ShouldNotifySoundEngine(EWorldType::Type WorldType) { return WorldType == EWorldType::PIE || WorldType == EWorldType::Game; } void FAkAudioDevice::LoadAudioObjectsAfterInitialization(TWeakObjectPtr&& InAudioType) { AudioObjectsToLoadAfterInitialization.Add(InAudioType); } void FAkAudioDevice::LoadDelayedObjects() { if (AudioObjectsToLoadAfterInitialization.Num() > 0) { SCOPED_AKAUDIO_EVENT_F_3(TEXT("LoadDelayedObjects: Loading %d Object%s"), (int)AudioObjectsToLoadAfterInitialization.Num(), AudioObjectsToLoadAfterInitialization.Num() > 1 ? TEXT("s") : TEXT("")); UE_LOG(LogAkAudio, Log, TEXT("FAkAudioDevice::LoadDelayedObjects: Loading %d delayed Wwise Object%s after initialization."), AudioObjectsToLoadAfterInitialization.Num(), AudioObjectsToLoadAfterInitialization.Num() > 1 ? TEXT("s") : TEXT("")); for (auto& WeakAudioType : AudioObjectsToLoadAfterInitialization) { auto* AudioType = WeakAudioType.Get(); if (LIKELY(AudioType)) { AudioType->LoadData(); } } AudioObjectsToLoadAfterInitialization.Empty(); } } namespace FAkAudioDevice_WaapiHelper { void Subscribe(FAkWaapiClient* waapiClient, uint64& subscriptionId, const char* uri, const TSharedRef& options, WampEventCallback callback) { if (subscriptionId == 0) { TSharedPtr result; waapiClient->Subscribe(uri, options, callback, subscriptionId, result); } } void RemoveCallback(FAkWaapiClient* waapiClient, uint64& subscriptionId) { if (subscriptionId > 0) { waapiClient->RemoveWampEventCallback(subscriptionId); subscriptionId = 0; } } void Unsubscribe(FAkWaapiClient* waapiClient, uint64& subscriptionId) { if (subscriptionId > 0) { TSharedPtr result; waapiClient->Unsubscribe(subscriptionId, result); RemoveCallback(waapiClient, subscriptionId); } } } /** * Initializes the audio device and creates sources. * * @return true if initialization was successful, false otherwise */ bool FAkAudioDevice::Init() { SCOPED_AKAUDIO_EVENT_2(TEXT("FAkAudioDevice::Init")); #if UE_SERVER return false; #endif if (!EnsureInitialized()) // ensure audiolib is initialized { UE_LOG(LogAkAudio, Log, TEXT("Audiokinetic Audio Device initialization failed.")); return false; } #if !WITH_EDITOR if (auto* akSettings = GetDefault()) { for (auto& entry : akSettings->UnrealCultureToWwiseCulture) { auto culturePtr = FInternationalization::Get().GetCulture(entry.Key); if (culturePtr && culturePtr->GetLCID() != InvariantLCID) { CachedUnrealToWwiseCulture.Add(culturePtr, entry.Value); } } } #endif #if AK_SUPPORT_WAAPI if (auto waapiClient = FAkWaapiClient::Get()) { ProjectLoadedHandle = waapiClient->OnProjectLoaded.AddLambda([this, waapiClient] { if (!waapiClient->IsConnected()) return; TSharedRef options = MakeShareable(new FJsonObject()); options->SetArrayField(WwiseWaapiHelper::RETURN, TArray> { MakeShareable(new FJsonValueString(WwiseWaapiHelper::PARENT)) }); auto wampEventCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr in_UEJsonObject) { AsyncTask(ENamedThreads::GameThread, [this] { #if WITH_EDITOR FEditorSupportDelegates::RedrawAllViewports.Broadcast(); #endif OnWwiseProjectModification.Broadcast(); }); }); FAkAudioDevice_WaapiHelper::Subscribe(waapiClient, WaapiSubscriptionIds.Renamed, ak::wwise::core::object::nameChanged, options, wampEventCallback); FAkAudioDevice_WaapiHelper::Subscribe(waapiClient, WaapiSubscriptionIds.PreDeleted, ak::wwise::core::object::preDeleted, options, wampEventCallback); FAkAudioDevice_WaapiHelper::Subscribe(waapiClient, WaapiSubscriptionIds.ChildRemoved, ak::wwise::core::object::childRemoved, options, wampEventCallback); FAkAudioDevice_WaapiHelper::Subscribe(waapiClient, WaapiSubscriptionIds.ChildAdded, ak::wwise::core::object::childAdded, options, wampEventCallback); FAkAudioDevice_WaapiHelper::Subscribe(waapiClient, WaapiSubscriptionIds.Created, ak::wwise::core::object::created, options, wampEventCallback); }); ConnectionLostHandle = waapiClient->OnConnectionLost.AddLambda([this, waapiClient] { if (!waapiClient->IsConnected()) return; FAkAudioDevice_WaapiHelper::RemoveCallback(waapiClient, WaapiSubscriptionIds.Renamed); FAkAudioDevice_WaapiHelper::RemoveCallback(waapiClient, WaapiSubscriptionIds.PreDeleted); FAkAudioDevice_WaapiHelper::RemoveCallback(waapiClient, WaapiSubscriptionIds.ChildRemoved); FAkAudioDevice_WaapiHelper::RemoveCallback(waapiClient, WaapiSubscriptionIds.ChildAdded); FAkAudioDevice_WaapiHelper::RemoveCallback(waapiClient, WaapiSubscriptionIds.Created); }); ClientBeginDestroyHandle = waapiClient->OnClientBeginDestroy.AddLambda([this, waapiClient] { if (ProjectLoadedHandle.IsValid()) { waapiClient->OnProjectLoaded.Remove(ProjectLoadedHandle); ProjectLoadedHandle.Reset(); } if (ConnectionLostHandle.IsValid()) { waapiClient->OnConnectionLost.Remove(ConnectionLostHandle); ConnectionLostHandle.Reset(); } }); } #endif // AK_SUPPORT_WAAPI FWorldDelegates::OnPreWorldInitialization.AddLambda( [&](UWorld* World, const UWorld::InitializationValues IVS) { WorldVolumesUpdatedMap.Add(World, false); } ); FWorldDelegates::OnPostWorldInitialization.AddLambda( [&](UWorld* World, const UWorld::InitializationValues IVS) { World->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateRaw(this, &FAkAudioDevice::OnActorSpawned)); } ); FWorldDelegates::OnWorldCleanup.AddLambda( [this](UWorld* World, bool bSessionEnded, bool bCleanupResources) { CleanupComponentMapsForWorld(World); if (WorldVolumesUpdatedMap.Contains(World)) WorldVolumesUpdatedMap.Remove(World); } ); FWorldDelegates::OnWorldPostActorTick.AddLambda( [&](UWorld* World, ELevelTick TickType, float DeltaSeconds) { if (WorldVolumesUpdatedMap.Contains(World)) WorldVolumesUpdatedMap[World] = false; else WorldVolumesUpdatedMap.Add(World, false); } ); m_SpatialAudioListener = nullptr; #if WITH_EDITORONLY_DATA if (!IsRunningGame()) { static const FName kLevelEditorModuleName = TEXT("LevelEditor"); auto MapChangedHandler = [this](UWorld* World, EMapChangeType MapChangeType) { if (World && World->AllowAudioPlayback() && World->WorldType == EWorldType::Editor) { if (MapChangeType == EMapChangeType::TearDownWorld) { if (EditorListener && World == EditorListener->GetWorld()) { EditorListener->DestroyComponent(); EditorListener = nullptr; } } else if (EditorListener == nullptr && MapChangeType != EMapChangeType::SaveMap) { EditorListener = CreateListener(World); // The Editor Listener should NEVER be the spatial audio listener if (m_SpatialAudioListener == EditorListener) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (LIKELY(SpatialAudio)) { SpatialAudio->UnregisterListener(m_SpatialAudioListener->GetAkGameObjectID()); } m_SpatialAudioListener = nullptr; } } } }; auto LevelEditorModulePtr = FModuleManager::Get().GetModulePtr(kLevelEditorModuleName); if (LevelEditorModulePtr) { LevelEditorModulePtr->OnMapChanged().AddLambda(MapChangedHandler); } else { FModuleManager::Get().OnModulesChanged().AddLambda([this, MapChangedHandler](FName InModuleName, EModuleChangeReason Reason) { if (InModuleName == kLevelEditorModuleName && Reason == EModuleChangeReason::ModuleLoaded) { auto& LevelEditorModule = FModuleManager::GetModuleChecked(kLevelEditorModuleName); LevelEditorModule.OnMapChanged().AddLambda(MapChangedHandler); } }); } FWorldDelegates::OnPostWorldInitialization.AddLambda( [this](UWorld* World, const UWorld::InitializationValues IVS) { if (World && World->AllowAudioPlayback() && World->WorldType == EWorldType::Editor && EditorListener == nullptr) { EditorListener = CreateListener(World); // The Editor Listener should NEVER be the spatial audio listener if (m_SpatialAudioListener == EditorListener) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (LIKELY(SpatialAudio)) { SpatialAudio->UnregisterListener(m_SpatialAudioListener->GetAkGameObjectID()); } m_SpatialAudioListener = nullptr; } } } ); FWorldDelegates::OnWorldTickStart.AddLambda( [&](UWorld* World, ELevelTick LevelTick, float Tick) { if(World->WorldType != EWorldType::EditorPreview) { return; } auto& ViewportClients = GEditor->GetAllViewportClients(); for(auto ViewportClient : ViewportClients) { if(ViewportClient->GetWorld() == World && ViewportClient->IsFocused(ViewportClient->Viewport)) { auto Quat = ViewportClient->GetViewRotation().Quaternion(); AkSoundPosition SoundPos; FVectorsToAKWorldTransform( ViewportClient->GetViewLocation(), Quat.GetForwardVector(), Quat.GetUpVector(), SoundPos ); SetPosition(EditorListener, SoundPos); } } } ); FEditorDelegates::OnEditorCameraMoved.AddLambda( [&](const FVector& Location, const FRotator& Rotation, ELevelViewportType ViewportType, int32 ViewIndex) { auto& allViewportClient = GEditor->GetAllViewportClients(); if (allViewportClient[ViewIndex]->Viewport && allViewportClient[ViewIndex]->Viewport->HasFocus()) { if (ListenerTransforms.Num() <= ViewIndex) { ListenerTransforms.AddDefaulted(ViewIndex - ListenerTransforms.Num() + 1); } ListenerTransforms[ViewIndex].SetLocation(Location); ListenerTransforms[ViewIndex].SetRotation(Rotation.Quaternion()); UWorld * ViewportWorld = allViewportClient[ViewIndex]->GetWorld(); if (ViewportWorld && ViewportWorld->WorldType != EWorldType::PIE) { auto Quat = Rotation.Quaternion(); AkSoundPosition SoundPos; FVectorsToAKWorldTransform( Location, Quat.GetForwardVector(), Quat.GetUpVector(), SoundPos ); SetPosition(EditorListener, SoundPos); } } } ); FEditorDelegates::BeginPIE.AddRaw(this, &FAkAudioDevice::BeginPIE); FEditorDelegates::EndPIE.AddRaw(this, &FAkAudioDevice::EndPIE); FEditorDelegates::PausePIE.AddRaw(this, &FAkAudioDevice::PausePIE); FEditorDelegates::ResumePIE.AddRaw(this, &FAkAudioDevice::ResumePie); FEditorDelegates::OnSwitchBeginPIEAndSIE.AddRaw(this, &FAkAudioDevice::OnSwitchBeginPIEAndSIE); } #endif UE_LOG(LogAkAudio, Log, TEXT("Audiokinetic Audio Device initialized.")); return 1; } #if WITH_EDITORONLY_DATA void FAkAudioDevice::BeginPIE(const bool bIsSimulating) { if (!bIsSimulating && EditorListener != nullptr) { RemoveDefaultListener(EditorListener); } else if (EditorListener == nullptr) { UE_LOG(LogAkAudio, Warning, TEXT("Undefined Behavior: EditorListener was set to null on BeginPIE.")); } } void FAkAudioDevice::PausePIE(const bool bIsSimulating) { for (auto& Event: EventToPlayingIDMap) { for (auto PlayingID: Event.Value) { EAkAudioContext* Context = PlayingIDToAudioContextMap.Find(PlayingID); if (Context) { if (*Context == EAkAudioContext::GameplayAudio) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (LIKELY(SoundEngine)) { SoundEngine->ExecuteActionOnPlayingID(AK::SoundEngine::AkActionOnEventType_Pause, PlayingID); } } } } } } void FAkAudioDevice::ResumePie(const bool bIsSimulating) { for (auto& Event: EventToPlayingIDMap) { for (auto PlayingID: Event.Value) { EAkAudioContext* Context = PlayingIDToAudioContextMap.Find(PlayingID); if (Context) { if (*Context == EAkAudioContext::GameplayAudio) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (LIKELY(SoundEngine)) { SoundEngine->ExecuteActionOnPlayingID(AK::SoundEngine::AkActionOnEventType_Resume, PlayingID); } } } } } } void FAkAudioDevice::OnSwitchBeginPIEAndSIE(const bool bIsSimulating) { if(EditorListener != nullptr) { if (bIsSimulating) { AddDefaultListener(EditorListener); // The Editor Listener should NEVER be the spatial audio listener if (m_SpatialAudioListener == EditorListener) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (LIKELY(SpatialAudio)) { SpatialAudio->UnregisterListener(m_SpatialAudioListener->GetAkGameObjectID()); } m_SpatialAudioListener = nullptr; } } else { RemoveDefaultListener(EditorListener); } } else { UE_LOG(LogAkAudio, Warning, TEXT("Undefined Behavior: EditorListener was set to null on SwitchBeginPIEAndSIE.")); } } void FAkAudioDevice::EndPIE(const bool bIsSimulating) { //Reset Unreal Global gameobject to avoid complications from removing Spatial Audio listener auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (LIKELY(SoundEngine)) { AkGameObjectID tempID = DUMMY_GAMEOBJ; SoundEngine->SetListeners(DUMMY_GAMEOBJ, &tempID, 1); } if (!bIsSimulating && EditorListener != nullptr) { AddDefaultListener(EditorListener); // The Editor Listener should NEVER be the spatial audio listener if (m_SpatialAudioListener == EditorListener) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (LIKELY(SpatialAudio)) { SpatialAudio->UnregisterListener(m_SpatialAudioListener->GetAkGameObjectID()); } m_SpatialAudioListener = nullptr; } } else if(EditorListener == nullptr) { UE_LOG(LogAkAudio, Warning, TEXT("Undefined Behavior: EditorListener was set to null on EndPIE.")); } StopAllSounds(); } #endif void FAkAudioDevice::UpdateRoomsForPortals() { #ifdef AK_ENABLE_ROOMS if (WorldsInNeedOfPortalRoomsUpdate.Num() == 0) { return; } for (auto& World : WorldsInNeedOfPortalRoomsUpdate) { auto Portals = WorldPortalsMap.Find(World); if (Portals != nullptr) { for (auto Portal : *Portals) { if (Portal.IsValid()) { const bool RoomsChanged = Portal->UpdateConnectedRooms(); if (RoomsChanged) SetSpatialAudioPortal(Portal.Get()); } } } } WorldsInNeedOfPortalRoomsUpdate.Empty(); #endif } void FAkAudioDevice::CleanupComponentMapsForWorld(UWorld* World) { LateReverbIndex.Clear(World); RoomIndex.Clear(World); WorldPortalsMap.Remove(World); OutdoorsConnectedPortals.Remove(World); } /** * Update the audio device and calculates the cached inverse transform later * on used for spatialization. */ bool FAkAudioDevice::Update( float DeltaTime ) { SCOPED_AKAUDIO_EVENT_2(TEXT("FAkAudioDevice::Update")); if (m_bSoundEngineInitialized) { UpdateRoomsForPortals(); // Suspend audio when not in VR focus if (FApp::UseVRFocus()) { if (FApp::HasVRFocus()) { WakeupFromSuspend(); } else { Suspend(true); } } UpdateSetCurrentAudioCultureAsyncTasks(); auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return false; SoundEngine->RenderAudio(); } return true; } /** * Tears down audio device by stopping all sounds, removing all buffers, * destroying all sources, ... Called by both Destroy and ShutdownAfterError * to perform the actual tear down. */ void FAkAudioDevice::Teardown() { SCOPED_AKAUDIO_EVENT_2(TEXT("FAkAudioDevice::Teardown")); #if AK_SUPPORT_WAAPI if (auto waapiClient = FAkWaapiClient::Get()) { FAkAudioDevice_WaapiHelper::Unsubscribe(waapiClient, WaapiSubscriptionIds.Renamed); FAkAudioDevice_WaapiHelper::Unsubscribe(waapiClient, WaapiSubscriptionIds.PreDeleted); FAkAudioDevice_WaapiHelper::Unsubscribe(waapiClient, WaapiSubscriptionIds.ChildRemoved); FAkAudioDevice_WaapiHelper::Unsubscribe(waapiClient, WaapiSubscriptionIds.ChildAdded); FAkAudioDevice_WaapiHelper::Unsubscribe(waapiClient, WaapiSubscriptionIds.Created); if (ProjectLoadedHandle.IsValid()) { waapiClient->OnProjectLoaded.Remove(ProjectLoadedHandle); ProjectLoadedHandle.Reset(); } if (ConnectionLostHandle.IsValid()) { waapiClient->OnConnectionLost.Remove(ConnectionLostHandle); ConnectionLostHandle.Reset(); } if (ClientBeginDestroyHandle.IsValid()) { waapiClient->OnClientBeginDestroy.Remove(ClientBeginDestroyHandle); ClientBeginDestroyHandle.Reset(); } OnWwiseProjectModification.Clear(); } #endif // AK_SUPPORT_WAAPI if (m_bSoundEngineInitialized) { m_EngineExiting = true; auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (LIKELY(SoundEngine)) { SoundEngine->UnregisterGameObj(DUMMY_GAMEOBJ); if (SoundEngine->IsInitialized()) { FAkAudioDevice_Helpers::UnregisterAllGlobalCallbacks(); SoundEngine->StopAll(); SoundEngine->RenderAudio(); } } FAkSoundEngineInitialization::Finalize(IOHook); if (LIKELY(CallbackManager)) { delete CallbackManager; CallbackManager = nullptr; } if (LIKELY(CallbackInfoPool)) { delete CallbackInfoPool; CallbackInfoPool = nullptr; } if (LIKELY(IOHook)) { delete IOHook; IOHook = nullptr; } auto* Monitor = IWwiseMonitorAPI::Get(); if (LIKELY(Monitor)) { Monitor->TerminateDefaultWAAPIErrorTranslator(); Monitor->ResetTranslator(); #if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) delete m_UnrealErrorTranslator; m_UnrealErrorTranslator = nullptr; #endif } m_bSoundEngineInitialized = false; } FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this); #if !WITH_EDITOR CachedUnrealToWwiseCulture.Empty(); #endif UE_LOG(LogAkAudio, Log, TEXT("Audiokinetic Audio Device terminated.")); } /** * Stops all game sounds (and possibly UI) sounds * * @param bShouldStopUISounds If true, this function will stop UI sounds as well */ void FAkAudioDevice::StopAllSounds(bool bShouldStopUISounds) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; FScopeLock Lock(&EventToPlayingIDMapCriticalSection); for (const auto& Event: EventToPlayingIDMap) { for (const auto& PlayingID: Event.Value) { if (EAkAudioContext* Context = PlayingIDToAudioContextMap.Find(PlayingID)) { if (*Context == EAkAudioContext::GameplayAudio) { SoundEngine->StopPlayingID(PlayingID); } if (bShouldStopUISounds && *Context == EAkAudioContext::EditorAudio) { SoundEngine->StopPlayingID(PlayingID); } } } } } void FAkAudioDevice::StopAllSounds(EAkAudioContext AudioContext) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; FScopeLock Lock(&EventToPlayingIDMapCriticalSection); for (const auto& Event: EventToPlayingIDMap) { for (const auto& PlayingID: Event.Value) { if (EAkAudioContext* Context = PlayingIDToAudioContextMap.Find(PlayingID)) { if (*Context == AudioContext) { SoundEngine->StopPlayingID(PlayingID); } } } } } /** * Stop all audio associated with a scene * * @param SceneToFlush Interface of the scene to flush */ void FAkAudioDevice::Flush(UWorld* WorldToFlush) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; StopAllSounds(); } /** * Determine if any rooms or reverb volumes have been added to World during the current frame */ bool FAkAudioDevice::WorldSpatialAudioVolumesUpdated(UWorld* World) { if (World == nullptr) return false; if (WorldVolumesUpdatedMap.Contains(World)) return WorldVolumesUpdatedMap[World]; return false; } /** * Clears all loaded SoundBanks and associated media * * @return Result from ak sound engine */ void FAkAudioDevice::ClearSoundBanksAndMedia() { if (m_bSoundEngineInitialized) { StopAllSounds(); auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (LIKELY(SoundEngine)) { SoundEngine->RenderAudio(); FPlatformProcess::Sleep(0.1f); } } for (TObjectIterator AudioAssetIt; AudioAssetIt; ++AudioAssetIt) { AudioAssetIt->UnloadData(); } } AKRESULT FAkAudioDevice::LoadBank( const FString& in_BankName, AkBankID& out_bankID ) { AKRESULT eResult = AK_Fail; if (EnsureInitialized()) // ensure audiolib is initialized { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->LoadBank(TCHAR_TO_AK(*in_BankName), out_bankID); } if (eResult != AK_Success) { UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::LoadBank: Failed to load bank %s. %s"), *in_BankName, WwiseUnrealHelper::GetResultString(eResult)); } return eResult; } AKRESULT FAkAudioDevice::LoadBank( const FString& in_BankName, AkBankCallbackFunc in_pfnBankCallback, void * in_pCookie, AkBankID & out_bankID ) { if (EnsureInitialized()) // ensure audiolib is initialized { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->LoadBank(TCHAR_TO_AK(*in_BankName), in_pfnBankCallback, in_pCookie, out_bankID); } return AK_Fail; } AKRESULT FAkAudioDevice::LoadBank( const FString& in_BankName, FWaitEndBankAction* LoadBankLatentAction ) { if (EnsureInitialized()) // ensure audiolib is initialized { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; AkBankID BankId; return SoundEngine->LoadBank(TCHAR_TO_AK(*in_BankName), nullptr, LoadBankLatentAction, BankId); } return AK_Fail; } AKRESULT FAkAudioDevice::LoadBankAsync( const FString& in_BankName, const FOnAkBankCallback& BankLoadedCallback, AkBankID & out_bankID ) { if (EnsureInitialized()) // ensure audiolib is initialized { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->LoadBank(TCHAR_TO_AK(*in_BankName), out_bankID); } return AK_Fail; } AKRESULT FAkAudioDevice::LoadBankFromMemory( const void* MemoryPtr, uint32 MemorySize, AkBankType BankType, AkBankID& OutBankID ) { if (EnsureInitialized() && MemoryPtr && MemorySize > 0) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->LoadBankMemoryView(MemoryPtr, MemorySize, OutBankID, BankType); } return AK_Fail; } AKRESULT FAkAudioDevice::UnloadBank( const FString& in_BankName ) { AKRESULT eResult = AK_Fail; if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->UnloadBank(TCHAR_TO_AK(*in_BankName), nullptr ); } if (eResult != AK_Success) { UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::UnloadBank: Failed to unload bank %s. %s"), *in_BankName, WwiseUnrealHelper::GetResultString(eResult)); } return eResult; } AKRESULT FAkAudioDevice::UnloadBank( const FString& in_BankName, AkBankCallbackFunc in_pfnBankCallback, void * in_pCookie ) { if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->UnloadBank(TCHAR_TO_AK(*in_BankName), NULL, in_pfnBankCallback, in_pCookie); } return AK_Fail; } AKRESULT FAkAudioDevice::UnloadBank( const FString& in_BankName, FWaitEndBankAction* UnloadBankLatentAction ) { if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->UnloadBank(TCHAR_TO_AK(*in_BankName), nullptr); } return AK_Fail; } AKRESULT FAkAudioDevice::UnloadBankFromMemory( AkBankID in_bankID, const void* in_memoryPtr ) { if (EnsureInitialized() && in_memoryPtr) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->UnloadBank(in_bankID, in_memoryPtr); } return AK_Fail; } AKRESULT FAkAudioDevice::UnloadBankFromMemoryAsync( AkBankID in_bankID, const void* in_memoryPtr, AkBankCallbackFunc in_pfnBankCallback, void* in_pCookie, uint32 BankType ) { if (EnsureInitialized() && in_memoryPtr) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->UnloadBank(in_bankID, in_memoryPtr, in_pfnBankCallback, in_pCookie, BankType); } return AK_Fail; } AKRESULT FAkAudioDevice::UnloadBankAsync( const FString& in_BankName, const FOnAkBankCallback& BankUnloadedCallback ) { if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->UnloadBank(TCHAR_TO_AK(*in_BankName), nullptr); } return AK_Fail; } AkUInt32 FAkAudioDevice::GetShortIDFromString(const FString& InString) { if (InString.IsEmpty()) { return AK_INVALID_UNIQUE_ID; } auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_INVALID_UNIQUE_ID; return SoundEngine->GetIDFromString(TCHAR_TO_ANSI(*InString)); } AkUInt32 FAkAudioDevice::GetShortID(UAkAudioType* AudioAsset, const FString& BackupName) { AkUInt32 ShortId; if (AudioAsset) { ShortId = AudioAsset->GetShortID(); } else { ShortId = GetShortIDFromString(BackupName); } if (ShortId == AK_INVALID_UNIQUE_ID) { UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetShortID : Returning invalid ShortId for Wwise Object named %s."), AudioAsset? *AudioAsset->GetName() : *BackupName); } return ShortId; } AKRESULT FAkAudioDevice::SetMedia(AkSourceSettings* in_pSourceSettings, uint32 in_uNumSourceSettings) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->SetMedia(in_pSourceSettings, in_uNumSourceSettings); } AKRESULT FAkAudioDevice::TryUnsetMedia(AkSourceSettings* in_pSourceSettings, uint32 in_uNumSourceSettings, AKRESULT* out_pUnsetResults) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->TryUnsetMedia(in_pSourceSettings, in_uNumSourceSettings, out_pUnsetResults); } AKRESULT FAkAudioDevice::UnsetMedia(AkSourceSettings* in_pSourceSettings, uint32 in_uNumSourceSettings) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->UnsetMedia(in_pSourceSettings, in_uNumSourceSettings); } FString FAkAudioDevice::GetCurrentAudioCulture() const { auto* StreamMgr = IWwiseStreamMgrAPI::Get(); if (UNLIKELY(!StreamMgr)) { return {}; } return FString(StreamMgr->GetCurrentLanguage()); } FString FAkAudioDevice::GetDefaultLanguage() { auto* WwiseInitBankLoader = FWwiseInitBankLoader::Get(); if (LIKELY(WwiseInitBankLoader)) { auto* InitBankAsset = WwiseInitBankLoader->GetInitBankAsset(); if (LIKELY(InitBankAsset)) { TArray Languages = InitBankAsset->GetLanguages(); for (auto& Language : Languages) { if (Language.LanguageRequirement == EWwiseLanguageRequirement::IsDefault) { return Language.LanguageName.ToString(); } } } else { UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetDefaultLanguage: Could not get AkInitBank asset, returning empty language.")); } } else { UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetDefaultLanguage: Could not get WwiseInitBankLoader, returning empty language.")); } UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetDefaultLanguage: Could not find default language in available languages list.")); return {}; } TArray FAkAudioDevice::GetAvailableAudioCultures() const { auto* WwiseInitBankLoader = FWwiseInitBankLoader::Get(); if (UNLIKELY(!WwiseInitBankLoader)) { UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetAvailableAudioCultures: Could not get WwiseInitBankLoader, returning empty list.")); return {}; } auto* InitBankAsset = WwiseInitBankLoader->GetInitBankAsset(); if (UNLIKELY(!InitBankAsset)) { UE_LOG(LogAkAudio, Warning, TEXT("FAkAudioDevice::GetAvailableAudioCultures: Could not get AkInitBank asset, returning empty list.")); return {}; } TSet LanguageNames; for(const auto& Language : InitBankAsset->InitBankCookedData.Language) { LanguageNames.Add(Language.LanguageName.ToString()); } return LanguageNames.Array(); } FWwiseLanguageCookedData FAkAudioDevice::GetLanguageCookedDataFromString(const FString& WwiseLanguage) { auto* WwiseInitBankLoader = FWwiseInitBankLoader::Get(); if (LIKELY(WwiseInitBankLoader)) { auto* InitBankAsset = WwiseInitBankLoader->GetInitBankAsset(); if (LIKELY(InitBankAsset)) { for (auto& Language : InitBankAsset->InitBankCookedData.Language) { if (Language.LanguageName.ToString() == WwiseLanguage) { return FWwiseLanguageCookedData(Language.LanguageId, Language.LanguageName, Language.LanguageRequirement); } } } } return FWwiseLanguageCookedData(GetShortIDFromString(WwiseLanguage), FName(WwiseLanguage), EWwiseLanguageRequirement::IsOptional); } void FAkAudioDevice::SetCurrentAudioCulture(const FString& NewAudioCulture) { FString NewWwiseLanguage; if (FindWwiseLanguage(NewAudioCulture, NewWwiseLanguage)) { auto* ResourceLoader = FWwiseResourceLoader::Get(); if (UNLIKELY(!ResourceLoader)) { return; } ResourceLoader->SetLanguage(GetLanguageCookedDataFromString(NewWwiseLanguage), EWwiseReloadLanguage::Immediate); auto* StreamMgr = IWwiseStreamMgrAPI::Get(); if (UNLIKELY(!StreamMgr)) { return; } StreamMgr->SetCurrentLanguage(TCHAR_TO_AK(*NewWwiseLanguage)); } } void FAkAudioDevice::SetCurrentAudioCultureAsync(const FString& NewAudioCulture, FSetCurrentAudioCultureAction* LatentAction) { FString NewWwiseLanguage; if (FindWwiseLanguage(NewAudioCulture, NewWwiseLanguage)) { SetCurrentAudioCultureAsyncTask* newTask = new SetCurrentAudioCultureAsyncTask(GetLanguageCookedDataFromString(NewWwiseLanguage), LatentAction); if (newTask->Start()) { AudioCultureAsyncTasks.Add(newTask); } else { LatentAction->ActionDone = true; delete newTask; } } else { LatentAction->ActionDone = true; } } void FAkAudioDevice::SetCurrentAudioCultureAsync(const FString& NewAudioCulture, const FOnSetCurrentAudioCultureCompleted& CompletedCallback) { FString NewWwiseLanguage; if (FindWwiseLanguage(NewAudioCulture, NewWwiseLanguage)) { SetCurrentAudioCultureAsyncTask* newTask = new SetCurrentAudioCultureAsyncTask(GetLanguageCookedDataFromString(NewWwiseLanguage), CompletedCallback); if (newTask->Start()) { AudioCultureAsyncTasks.Add(newTask); } else { CompletedCallback.ExecuteIfBound(false); delete newTask; } } else { CompletedCallback.ExecuteIfBound(true); } } bool FAkAudioDevice::FindWwiseLanguage(const FString& NewAudioCulture, FString& FoundWwiseLanguage) { auto oldAudioCulture = GetCurrentAudioCulture(); FoundWwiseLanguage = NewAudioCulture; auto newCulturePtr = FInternationalization::Get().GetCulture(FoundWwiseLanguage); if (newCulturePtr && newCulturePtr->GetLCID() != InvariantLCID) { auto& newLanguage = newCulturePtr->GetTwoLetterISOLanguageName(); auto& newRegion = newCulturePtr->GetRegion(); auto& newScript = newCulturePtr->GetScript(); int maxMatch = 0; auto findMostCompatibleCulture = [&maxMatch, &newLanguage, &newRegion, &newScript, &FoundWwiseLanguage](const FCulturePtr& currentCulture, const FString& currentValue) { auto& currentLanguageName = currentCulture->GetTwoLetterISOLanguageName(); auto& currentRegionName = currentCulture->GetRegion(); auto& currentScript = currentCulture->GetScript(); int currentMatch = 0; if (currentLanguageName == newLanguage) { currentMatch = 1; // This is inspired by how UE processes culture from most to least specific. For example: // zh - Hans - CN is processed as "zh-Hans-CN", then "zh-CN", then "zh-Hans", then "zh". // This is how I selected the weight for each match. if (!currentScript.IsEmpty() && !newScript.IsEmpty() && !currentRegionName.IsEmpty() && !newRegion.IsEmpty()) { if (currentScript == newScript && currentRegionName == newRegion) { currentMatch += 4; } else { --currentMatch; } } if (!currentRegionName.IsEmpty() && !newRegion.IsEmpty()) { if (currentRegionName == newRegion) { currentMatch += 3; } else { --currentMatch; } } if (!currentScript.IsEmpty() && !newScript.IsEmpty()) { if (currentScript == newScript) { currentMatch += 2; } else { --currentMatch; } } } if (currentMatch > 0) { // When the current culture is missing script or region but the new culture // has it, give a weight boost of 1 if (!newScript.IsEmpty() && currentScript.IsEmpty()) { ++currentMatch; } if (!newRegion.IsEmpty() && currentRegionName.IsEmpty()) { ++currentMatch; } } if (maxMatch < currentMatch) { FoundWwiseLanguage = currentValue; maxMatch = currentMatch; } }; #if WITH_EDITOR if (auto* akSettings = GetDefault()) { for (auto& entry : akSettings->UnrealCultureToWwiseCulture) { auto currentCulture = FInternationalization::Get().GetCulture(entry.Key); if (currentCulture && currentCulture->GetLCID() != InvariantLCID) { findMostCompatibleCulture(currentCulture, entry.Value); } } } #else for (auto& entry : CachedUnrealToWwiseCulture) { findMostCompatibleCulture(entry.Key, entry.Value); } #endif } return oldAudioCulture != FoundWwiseLanguage; } void FAkAudioDevice::UpdateSetCurrentAudioCultureAsyncTasks() { if(AudioCultureAsyncTasks.Num() == 0) { return; } for (auto task : AudioCultureAsyncTasks) { task->Update(); } for (int32 i = AudioCultureAsyncTasks.Num() - 1; i >= 0; --i) { if (AudioCultureAsyncTasks[i]->IsDone) { delete AudioCultureAsyncTasks[i]; AudioCultureAsyncTasks[i] = nullptr; } } AudioCultureAsyncTasks.RemoveAll([](SetCurrentAudioCultureAsyncTask* Task) { return Task == nullptr; }); } template AkPlayingID FAkAudioDevice::PostEventWithCallbackPackageOnAkGameObject( const AkUInt32 EventShortID, UAkGameObject* GameObject, const TArray& ExternalSources, FCreateCallbackPackage CreateCallbackPackage, EAkAudioContext AudioContext ) { AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; if (m_bSoundEngineInitialized && GameObject && CallbackManager) { if (EventShortID != AK_INVALID_UNIQUE_ID && GameObject->AllowAudioPlayback()) { GameObject->UpdateObstructionAndOcclusion(); auto gameObjID = GameObject->GetAkGameObjectID(); PlayingID = PostEventWithCallbackPackageOnGameObjectId(EventShortID, gameObjID, ExternalSources, CreateCallbackPackage, AudioContext); if (PlayingID != AK_INVALID_PLAYING_ID) { GameObject->EventPosted(); } } } return PlayingID; } void FAkAudioDevice::SAComponentAddedRemoved(UWorld* World) { if (World != nullptr) { if (WorldVolumesUpdatedMap.Contains(World)) WorldVolumesUpdatedMap[World] = true; else WorldVolumesUpdatedMap.Add(World, true); } } /** Find UAkLateReverbComponents at a given location. */ TArray FAkAudioDevice::FindLateReverbComponentsAtLocation(const FVector& Loc, const UWorld* World) { return LateReverbIndex.Query(Loc, World); } /** Add a UAkLateReverbComponent to the linked list. */ void FAkAudioDevice::IndexLateReverb(class UAkLateReverbComponent* ComponentToAdd) { check(!ComponentToAdd->IsIndexed); LateReverbIndex.Add(ComponentToAdd); SAComponentAddedRemoved(ComponentToAdd->GetWorld()); ComponentToAdd->IsIndexed = true; } /** Remove a UAkLateReverbComponent from the linked list. */ void FAkAudioDevice::UnindexLateReverb(class UAkLateReverbComponent* ComponentToRemove) { check(ComponentToRemove->IsIndexed); if (LateReverbIndex.Remove(ComponentToRemove)) { SAComponentAddedRemoved(ComponentToRemove->GetWorld()); } ComponentToRemove->IsIndexed = false; } void FAkAudioDevice::ReindexLateReverb(class UAkLateReverbComponent* ComponentToUpdate) { check(ComponentToUpdate->IsIndexed); LateReverbIndex.Update(ComponentToUpdate); SAComponentAddedRemoved(ComponentToUpdate->GetWorld()); } bool FAkAudioDevice::WorldHasActiveRooms(UWorld* World) { return !RoomIndex.IsEmpty(World); } /** Find UAkRoomComponents at a given location. */ TArray FAkAudioDevice::FindRoomComponentsAtLocation(const FVector& Loc, const UWorld* World) { return RoomIndex.Query(Loc, World); } /** Add a UAkRoomComponent to the linked list. */ void FAkAudioDevice::IndexRoom(class UAkRoomComponent* ComponentToAdd) { RoomIndex.Add(ComponentToAdd); SAComponentAddedRemoved(ComponentToAdd->GetWorld()); } /** Remove a UAkRoomComponent from the linked list. */ void FAkAudioDevice::UnindexRoom(class UAkRoomComponent* ComponentToRemove) { if (RoomIndex.Remove(ComponentToRemove)) { SAComponentAddedRemoved(ComponentToRemove->GetWorld()); } } void FAkAudioDevice::ReindexRoom(class UAkRoomComponent* ComponentToAdd) { RoomIndex.Update(ComponentToAdd); SAComponentAddedRemoved(ComponentToAdd->GetWorld()); } /** Return true if any UAkRoomComponents have been added to the prioritized list of rooms **/ bool FAkAudioDevice::UsingSpatialAudioRooms(const UWorld* World) { return !RoomIndex.IsEmpty(World); } /** Seek on an event in the ak soundengine. * @param EventShortID Name of the event on which to seek. * @param Actor The associated Actor. If this is nullptr, defaul object will be used. * @param in_fPercent Desired percent where playback should restart. * @param in_bSeekToNearestMarker If true, the final seeking position will be made equal to the nearest marker. * * @return Success or failure. */ AKRESULT FAkAudioDevice::SeekOnEvent( const AkUInt32 EventShortID, AActor* Actor, AkReal32 Percent, bool bSeekToNearestMarker /*= false*/, AkPlayingID PlayingID /*= AK_INVALID_PLAYING_ID*/ ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; if (!Actor) { // SeekOnEvent must be bound to a game object. Passing DUMMY_GAMEOBJ as default game object. return SoundEngine->SeekOnEvent(EventShortID, DUMMY_GAMEOBJ, Percent, bSeekToNearestMarker, PlayingID); } else if (!Actor->IsActorBeingDestroyed() && IsValid(Actor)) { UAkComponent* pComponent = GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset); if (pComponent) { return SeekOnEvent(EventShortID, pComponent, Percent, bSeekToNearestMarker, PlayingID); } } return AKRESULT::AK_Fail; } /** Seek on an event in the ak soundengine. * @param EventShortID Name of the event on which to seek. * @param Component The associated AkComponent. * @param in_fPercent Desired percent where playback should restart. * @param in_bSeekToNearestMarker If true, the final seeking position will be made equal to the nearest marker. * * @return Success or failure. */ AKRESULT FAkAudioDevice::SeekOnEvent( const AkUInt32 EventShortID, UAkComponent* Component, AkReal32 Percent, bool bSeekToNearestMarker /*= false*/, AkPlayingID PlayingID /*= AK_INVALID_PLAYING_ID*/ ) { if (m_bSoundEngineInitialized && Component) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; if (Component->AllowAudioPlayback()) { return SoundEngine->SeekOnEvent(EventShortID, Component->GetAkGameObjectID(), Percent, bSeekToNearestMarker, PlayingID); } } return AKRESULT::AK_Fail; } void FAkAudioDevice::UpdateAllSpatialAudioPortals(UWorld* InWorld) { #ifdef AK_ENABLE_PORTALS auto Portals = WorldPortalsMap.Find(InWorld); if (Portals != nullptr) { for (auto Portal : *Portals) { if (Portal.IsValid()) { SetSpatialAudioPortal(Portal.Get()); } } } #endif } void FAkAudioDevice::SetSpatialAudioPortal(UAkPortalComponent* in_Portal) { if(!IsValid(in_Portal) || IsRunningCommandlet()) return; auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return; #ifdef AK_ENABLE_PORTALS UWorld* World = in_Portal->GetWorld(); auto Portals = WorldPortalsMap.Find(World); if (Portals == nullptr) Portals = &WorldPortalsMap.Add(World, TArray>()); if (Portals != nullptr) { if (!Portals->Contains(in_Portal)) { Portals->Add(in_Portal); } } /* Only update the sound engine for game worlds. */ if (ShouldNotifySoundEngine(World->WorldType)) { AkPortalID portalID = in_Portal->GetPortalID(); if (!in_Portal->PortalPlacementValid()) { SpatialAudio->RemovePortal(portalID); UE_LOG(LogAkAudio, Log, TEXT("UAkPortalComponent %s must have a front room which is distinct from its back room."), *(in_Portal->GetOwner() != nullptr ? in_Portal->GetOwner()->GetName() : in_Portal->GetName())); } else { FString nameStr = in_Portal->GetName(); AActor* portalOwner = in_Portal->GetOwner(); UPrimitiveComponent* primitiveParent = in_Portal->GetPrimitiveParent(); if (portalOwner != nullptr) { #if WITH_EDITOR nameStr = portalOwner->GetActorLabel(); #else nameStr = portalOwner->GetName(); #endif if (primitiveParent != nullptr) { // ensures unique and meaningful names when we have multiple portals in the same actor. TInlineComponentArray PortalComponents; portalOwner->GetComponents(PortalComponents); if (PortalComponents.Num() > 1) nameStr.Append(FString("_").Append(primitiveParent->GetName())); } } AkPortalParams Params; UPrimitiveComponent* Parent = in_Portal->GetPrimitiveParent(); if (IsValid(Parent)) { AkComponentHelpers::GetPrimitiveTransformAndExtent(*Parent, Params.Transform, Params.Extent); } Params.bEnabled = in_Portal->GetCurrentState() == AkAcousticPortalState::Open; Params.FrontRoom = in_Portal->GetFrontRoomID(); Params.BackRoom = in_Portal->GetBackRoomID(); SpatialAudio->SetPortal(portalID, Params, TCHAR_TO_ANSI(*nameStr)); } } #endif } void FAkAudioDevice::RemoveSpatialAudioPortal(UAkPortalComponent* in_Portal) { if (IsRunningCommandlet()) return; auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return; #ifdef AK_ENABLE_PORTALS auto Portals = WorldPortalsMap.Find(in_Portal->GetWorld()); if (Portals != nullptr && Portals->Contains(in_Portal)) { Portals->Remove(in_Portal); } if (ShouldNotifySoundEngine(in_Portal->GetWorld()->WorldType)) { AkPortalID portalID = in_Portal->GetPortalID(); SpatialAudio->RemovePortal(portalID); } #endif } /** Get a sorted list of AkAuxSendValue at a location * * @param Loc Location at which to find Reverb Volumes * @param AkReverbVolumes Array of AkAuxSendValue at this location */ void FAkAudioDevice::GetAuxSendValuesAtLocation(FVector Loc, TArray& AkAuxSendValues, const UWorld* in_World) { // Check if there are AkReverbVolumes at this location TArray FoundComponents = LateReverbIndex.Query(Loc, in_World); // Sort the found Volumes if (FoundComponents.Num() > 1) { FoundComponents.Sort([](const UAkLateReverbComponent& A, const UAkLateReverbComponent& B) { return A.Priority > B.Priority; }); } // Apply the found Aux Sends AkAuxSendValue TmpSendValue; // Build a list to set as AuxBusses for( uint8 Idx = 0; Idx < FoundComponents.Num() && Idx < MaxAuxBus; Idx++ ) { TmpSendValue.listenerID = AK_INVALID_GAME_OBJECT; TmpSendValue.auxBusID = FoundComponents[Idx]->GetAuxBusId(); TmpSendValue.fControlValue = FoundComponents[Idx]->SendLevel; AkAuxSendValues.Add(TmpSendValue); } } void FAkAudioDevice::PostEventAtLocationEndOfEventCallback(AkCallbackType in_eType, AkCallbackInfo* in_pCallbackInfo) { if (auto* Device = FAkAudioDevice::Get()) { Device->RemovePlayingID(((AkEventCallbackInfo*)in_pCallbackInfo)->eventID, ((AkEventCallbackInfo*)in_pCallbackInfo)->playingID); auto pPackage = (IAkUserEventCallbackPackage*)in_pCallbackInfo->pCookie; if (pPackage && pPackage->HasExternalSources) { if (auto* ExternalSourceManager = IWwiseExternalSourceManager::Get()) { ExternalSourceManager->OnEndOfEvent(((AkEventCallbackInfo*)in_pCallbackInfo)->playingID); } } } } UAkComponent* FAkAudioDevice::SpawnAkComponentAtLocation( class UAkAudioEvent* in_pAkEvent, FVector Location, FRotator Orientation, bool AutoPost, const FString& EventName, bool AutoDestroy, UWorld* in_World) { UAkComponent * AkComponent = NULL; if (in_World) { AkComponent = NewObject(in_World->GetWorldSettings()); } else { AkComponent = NewObject(); } if( AkComponent ) { AkComponent->AkAudioEvent = in_pAkEvent; AkComponent->SetWorldLocationAndRotation(Location, Orientation.Quaternion()); if(in_World) { AkComponent->RegisterComponentWithWorld(in_World); } AkComponent->SetAutoDestroy(AutoDestroy); if(AutoPost) { if (AkComponent->PostAssociatedAkEvent(0, FOnAkPostEventCallback()) == AK_INVALID_PLAYING_ID && AutoDestroy) { AkComponent->ConditionalBeginDestroy(); AkComponent = NULL; } } } return AkComponent; } /** * Post a trigger to ak soundengine * * @param in_pszTrigger Name of the trigger * @param in_pAkComponent AkComponent on which to post the trigger * @return Result from ak sound engine */ AKRESULT FAkAudioDevice::PostTrigger( const TCHAR * in_pszTrigger, AActor * in_pActor ) { AkGameObjectID GameObjID = AK_INVALID_GAME_OBJECT; AKRESULT eResult = GetGameObjectID( in_pActor, GameObjID ); if ( m_bSoundEngineInitialized && eResult == AK_Success) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->PostTrigger(TCHAR_TO_AK(in_pszTrigger), GameObjID ); } return eResult; } AKRESULT FAkAudioDevice::PostTrigger( const UAkTrigger* in_TriggerValue, AActor* in_pActor ) { AkGameObjectID GameObjID = AK_INVALID_GAME_OBJECT; AKRESULT eResult = GetGameObjectID(in_pActor, GameObjID); if (m_bSoundEngineInitialized && in_TriggerValue && eResult == AK_Success) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->PostTrigger(in_TriggerValue->GetShortID(), GameObjID); } return eResult; } /** * Set a RTPC in ak soundengine * * @param in_pszRtpcName Name of the RTPC * @param in_value Value to set * @param in_pActor Actor on which to set the RTPC * @return Result from ak sound engine */ AKRESULT FAkAudioDevice::SetRTPCValue( const TCHAR * in_pszRtpcName, AkRtpcValue in_value, int32 in_interpolationTimeMs = 0, AActor * in_pActor = NULL ) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; auto RtpcID = SoundEngine->GetIDFromString(TCHAR_TO_AK(in_pszRtpcName)); eResult = SetRTPCValue(RtpcID, in_value, in_interpolationTimeMs, in_pActor); } return eResult; } /** * Set a RTPC in ak soundengine * * @param in_Rtpc RTPC Short ID * @param in_value Value to set * @param in_interpolationTimeMs - Duration during which the RTPC is interpolated towards in_value (in ms) * @param in_pActor AActor on which to set the RTPC * @return Result from ak sound engine */ AKRESULT FAkAudioDevice::SetRTPCValue( AkRtpcID in_Rtpc, AkRtpcValue in_value, int32 in_interpolationTimeMs = 0, AActor * in_pActor = NULL ) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; AkGameObjectID GameObjID = AK_INVALID_GAME_OBJECT; // RTPC at global scope is supported if (in_pActor) { eResult = GetGameObjectID(in_pActor, GameObjID); if (eResult != AK_Success) return eResult; } eResult = SoundEngine->SetRTPCValue(in_Rtpc, in_value, GameObjID, in_interpolationTimeMs); } return eResult; } AKRESULT FAkAudioDevice::SetRTPCValue( const UAkRtpc* in_RtpcValue, AkRtpcValue in_value, int32 in_interpolationTimeMs = 0, AActor * in_pActor = NULL ) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized && in_RtpcValue) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; AkGameObjectID GameObjID = AK_INVALID_GAME_OBJECT; // RTPC at global scope is supported if (in_pActor) { eResult = GetGameObjectID(in_pActor, GameObjID); if (eResult != AK_Success) return eResult; } eResult = SoundEngine->SetRTPCValue(in_RtpcValue->GetShortID(), in_value, GameObjID, in_interpolationTimeMs); } return eResult; } AKRESULT FAkAudioDevice::SetRTPCValueByPlayingID( AkRtpcID in_Rtpc, AkRtpcValue in_value, AkPlayingID in_playingID, int32 in_interpolationTimeMs ) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetRTPCValueByPlayingID(in_Rtpc, in_value, in_playingID, in_interpolationTimeMs); } return eResult; } /** * Get the value of a real-time parameter control (by ID) * An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. * The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. * If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. * @note * When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. * If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. * However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. * * @return AK_Success if succeeded, AK_IDNotFound if the game object was not registered, or AK_Fail if the RTPC value could not be obtained */ AKRESULT FAkAudioDevice::GetRTPCValue( const TCHAR * in_pszRtpcName, AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. AkRtpcValue& out_rValue, ///< Value returned AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ); ) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->Query->GetRTPCValue(TCHAR_TO_AK(in_pszRtpcName), in_gameObjectID, in_playingID, out_rValue, io_rValueType); } return eResult; } /** * Get the value of a real-time parameter control (by ID) * An RTPC can have a any combination of a global value, a unique value for each game object, or a unique value for each playing ID. * The value requested is determined by RTPCValue_type, in_gameObjectID and in_playingID. * If a value at the requested scope (determined by RTPCValue_type) is not found, the value that is available at the the next broadest scope will be returned, and io_rValueType will be changed to indicate this. * @note * When looking up RTPC values via playing ID (ie. io_rValueType is RTPC_PlayingID), in_gameObjectID can be set to a specific game object (if it is available to the caller) to use as a fall back value. * If the game object is unknown or unavailable, AK_INVALID_GAME_OBJECT can be passed in in_gameObjectID, and the game object will be looked up via in_playingID. * However in this case, it is not possible to retrieve a game object value as a fall back value if the playing id does not exist. It is best to pass in the game object if possible. * * @return AK_Success if succeeded, AK_IDNotFound if the game object was not registered, or AK_Fail if the RTPC value could not be obtained */ AKRESULT FAkAudioDevice::GetRTPCValue( AkRtpcID in_Rtpc, AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. AkRtpcValue& out_rValue, ///< Value returned AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ); ) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->Query->GetRTPCValue(in_Rtpc, in_gameObjectID, in_playingID, out_rValue, io_rValueType); } return eResult; } AKRESULT FAkAudioDevice::GetRTPCValue( const UAkRtpc* in_RtpcValue, AkGameObjectID in_gameObjectID, ///< Associated game object ID, ignored if io_rValueType is RTPCValue_Global. AkPlayingID in_playingID, ///< Associated playing ID, ignored if io_rValueType is not RTPC_PlayingID. AkRtpcValue& out_rValue, ///< Value returned AK::SoundEngine::Query::RTPCValue_type& io_rValueType ///< In/Out value, the user must specify the requested type. The function will return in this variable the type of the returned value. ); ) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized && in_RtpcValue) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->Query->GetRTPCValue(in_RtpcValue->GetShortID(), in_gameObjectID, in_playingID, out_rValue, io_rValueType); } return eResult; } AKRESULT FAkAudioDevice::ResetRTPCValue(const UAkRtpc* in_RtpcValue, AkGameObjectID in_gameObjectID, int32 in_interpolationTimeMs) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized && in_RtpcValue) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->ResetRTPCValue(in_RtpcValue->GetShortID(), in_gameObjectID, in_interpolationTimeMs); } return eResult; } AKRESULT FAkAudioDevice::ResetRTPCValue(AkRtpcID in_rtpcID, AkGameObjectID in_gameObjectID, int32 in_interpolationTimeMs) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized && in_rtpcID) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->ResetRTPCValue(in_rtpcID, in_gameObjectID, in_interpolationTimeMs); } return eResult; } AKRESULT FAkAudioDevice::ResetRTPCValue(const TCHAR* in_pszRtpcName, AkGameObjectID in_gameObjectID, int32 in_interpolationTimeMs) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->ResetRTPCValue(TCHAR_TO_AK(in_pszRtpcName), in_gameObjectID, in_interpolationTimeMs); } return eResult; } /** * Set a state in ak soundengine * * @param in_pszStateGroup Name of the state group * @param in_pszState Name of the state * @return Result from ak sound engine */ AKRESULT FAkAudioDevice::SetState( const TCHAR * in_pszStateGroup, const TCHAR * in_pszState ) { AKRESULT eResult = AK_Success; if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; auto StateGroupID = SoundEngine->GetIDFromString(TCHAR_TO_AK(in_pszStateGroup)); auto StateID = SoundEngine->GetIDFromString(TCHAR_TO_AK(in_pszState)); eResult = SoundEngine->SetState(StateGroupID, StateID); } return eResult; } /** * Set a state in ak soundengine * * @param in_StateGroup State group short ID * @param in_State State short ID * @return Result from ak sound engine */ AKRESULT FAkAudioDevice::SetState( AkStateGroupID in_StateGroup, AkStateID in_State ) { AKRESULT eResult = AK_Success; if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetState(in_StateGroup, in_State); } return eResult; } AKRESULT FAkAudioDevice::SetState( const UAkStateValue* in_stateValue ) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized && in_stateValue) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetState(in_stateValue->GetGroupID(), in_stateValue->GetShortID()); } return eResult; } /** * Set a switch in ak soundengine * * @param in_pszSwitchGroup Name of the switch group * @param in_pszSwitchState Name of the switch * @param in_pComponent AkComponent on which to set the switch * @return Result from ak sound engine */ AKRESULT FAkAudioDevice::SetSwitch( const TCHAR * in_pszSwitchGroup, const TCHAR * in_pszSwitchState, AActor * in_pActor ) { AKRESULT eResult = AK_Success; if ( m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; auto SwitchGroupID = SoundEngine->GetIDFromString(TCHAR_TO_AK(in_pszSwitchGroup)); auto SwitchStateID = SoundEngine->GetIDFromString(TCHAR_TO_AK(in_pszSwitchState)); eResult = SetSwitch(SwitchGroupID, SwitchStateID, in_pActor); } return eResult; } /** * Set a switch in ak soundengine * * @param in_SwitchGroup Short ID of the switch group * @param in_SwitchState Short ID of the switch * @param in_pComponent AkComponent on which to set the switch * @return Result from ak sound engine */ AKRESULT FAkAudioDevice::SetSwitch( AkSwitchGroupID in_SwitchGroup, AkSwitchStateID in_SwitchState, AActor * in_pActor ) { AkGameObjectID GameObjID = DUMMY_GAMEOBJ; // Switches must be bound to a game object. passing DUMMY_GAMEOBJ as default game object. AKRESULT eResult = GetGameObjectID( in_pActor, GameObjID ); if ( m_bSoundEngineInitialized && eResult == AK_Success) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetSwitch(in_SwitchGroup, in_SwitchState, GameObjID); } return eResult; } AKRESULT FAkAudioDevice::SetSwitch( const UAkSwitchValue* in_switchValue, AActor * in_pActor ) { AkGameObjectID GameObjID = DUMMY_GAMEOBJ; // Switches must be bound to a game object. passing DUMMY_GAMEOBJ as default game object. AKRESULT eResult = GetGameObjectID(in_pActor, GameObjID); if (m_bSoundEngineInitialized && in_switchValue && eResult == AK_Success) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetSwitch(in_switchValue->GetGroupID(), in_switchValue->GetShortID(), GameObjID); } return eResult; } static AK::SoundEngine::MultiPositionType GetSoundEngineMultiPositionType(AkMultiPositionType in_eType) { switch (in_eType) { case AkMultiPositionType::SingleSource: return AK::SoundEngine::MultiPositionType_SingleSource; case AkMultiPositionType::MultiSources: return AK::SoundEngine::MultiPositionType_MultiSources; case AkMultiPositionType::MultiDirections: return AK::SoundEngine::MultiPositionType_MultiDirections; // Unknown multi position type! default: check(false); return AK::SoundEngine::MultiPositionType_SingleSource; } } /** Sets multiple positions to a single game object. * Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. * This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. * Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() * @param in_pGameObjectAkComponent Game Object AkComponent. * @param in_pPositions Array of positions to apply. * @param in_eMultiPositionType Position type * @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. * */ AKRESULT FAkAudioDevice::SetMultiplePositions( UAkComponent* in_pGameObjectAkComponent, TArray in_aPositions, AkMultiPositionType in_eMultiPositionType /*= AkMultiPositionType::MultiDirections*/ ) { if (!in_pGameObjectAkComponent) { return AK_Fail; } auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; const int numPositions = in_aPositions.Num(); TArray aPositions; aPositions.Empty(); for (int i = 0; i < numPositions; ++i) { AkSoundPosition soundpos; FAkAudioDevice::FVectorsToAKWorldTransform(in_aPositions[i].GetLocation(), in_aPositions[i].GetRotation().GetForwardVector(), in_aPositions[i].GetRotation().GetUpVector(), soundpos); aPositions.Add(soundpos); } return SoundEngine->SetMultiplePositions(in_pGameObjectAkComponent->GetAkGameObjectID(), aPositions.GetData(), aPositions.Num(), GetSoundEngineMultiPositionType(in_eMultiPositionType)); } template AKRESULT FAkAudioDevice::SetMultiplePositions( UAkComponent* in_pGameObjectAkComponent, const TArray& in_aChannelConfigurations, const TArray& in_aPositions, AkMultiPositionType in_eMultiPositionType /*= AkMultiPositionType::MultiDirections*/ ) { if (!in_pGameObjectAkComponent) { return AK_Fail; } auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; const int32 numPositions = FMath::Min(in_aPositions.Num(), in_aChannelConfigurations.Num()); TArray emitters; emitters.Reserve(numPositions); for (int i = 0; i < numPositions; ++i) { AkSoundPosition soundpos; FAkAudioDevice::FVectorsToAKWorldTransform(in_aPositions[i].GetLocation(), in_aPositions[i].GetRotation().GetForwardVector(), in_aPositions[i].GetRotation().GetUpVector(), soundpos); AkChannelConfig config; GetChannelConfig(in_aChannelConfigurations[i], config); emitters.Add(AkChannelEmitter()); emitters[i].uInputChannels = config.uChannelMask; emitters[i].position = soundpos; } return SoundEngine->SetMultiplePositions(in_pGameObjectAkComponent->GetAkGameObjectID(), emitters.GetData(), emitters.Num(), GetSoundEngineMultiPositionType(in_eMultiPositionType)); } AKRESULT FAkAudioDevice::SetMultiplePositions( UAkComponent* in_pGameObjectAkComponent, const TArray& in_aChannelConfigurations, const TArray& in_aPositions, AkMultiPositionType in_eMultiPositionType ) { return SetMultiplePositions(in_pGameObjectAkComponent, in_aChannelConfigurations, in_aPositions, in_eMultiPositionType); } AKRESULT FAkAudioDevice::SetMultiplePositions( UAkComponent* in_pGameObjectAkComponent, const TArray& in_channelMasks, const TArray& in_aPositions, AkMultiPositionType in_eMultiPositionType ) { return SetMultiplePositions(in_pGameObjectAkComponent, in_channelMasks, in_aPositions, in_eMultiPositionType); } /** Sets multiple positions to a single game object. * Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. * This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. * Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() * @param in_GameObjectID Game Object identifier. * @param in_pPositions Array of positions to apply. * @param in_NumPositions Number of positions specified in the provided array. * @param in_eMultiPositionType Position type * @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. */ AKRESULT FAkAudioDevice::SetMultiplePositions( AkGameObjectID in_GameObjectID, const AkSoundPosition * in_pPositions, AkUInt16 in_NumPositions, AK::SoundEngine::MultiPositionType in_eMultiPositionType /*= AK::SoundEngine::MultiDirections*/ ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->SetMultiplePositions(in_GameObjectID, in_pPositions, in_NumPositions, in_eMultiPositionType); } /** Sets multiple positions to a single game object, with flexible assignment of input channels. * Setting multiple positions on a single game object is a way to simulate multiple emission sources while using the resources of only one voice. * This can be used to simulate wall openings, area sounds, or multiple objects emitting the same sound in the same area. * Note: Calling AK::SoundEngine::SetMultiplePositions() with only one position is the same as calling AK::SoundEngine::SetPosition() * @param in_GameObjectID Game Object identifier. * @param in_pPositions Array of positions to apply. * @param in_NumPositions Number of positions specified in the provided array. * @param in_eMultiPositionType Position type * @return AK_Success when successful, AK_InvalidParameter if parameters are not valid. */ AKRESULT FAkAudioDevice::SetMultiplePositions( AkGameObjectID in_GameObjectID, const AkChannelEmitter * in_pPositions, AkUInt16 in_NumPositions, AK::SoundEngine::MultiPositionType in_eMultiPositionType /*= AK::SoundEngine::MultiDirections*/ ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->SetMultiplePositions(in_GameObjectID, in_pPositions, in_NumPositions, in_eMultiPositionType); } /** * Set auxiliary sends * * @param in_GameObjId Wwise Game Object ID * @param in_AuxSendValues Array of AkAuxSendValue, containins all Aux Sends to set on the game objectt * @return Result from ak sound engine */ AKRESULT FAkAudioDevice::SetAuxSends( const UAkComponent* in_akComponent, TArray& in_AuxSendValues ) { AKRESULT eResult = AK_Success; if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetGameObjectAuxSendValues(in_akComponent->GetAkGameObjectID(), in_AuxSendValues.GetData(), in_AuxSendValues.Num()); } return eResult; } void FAkAudioDevice::GetChannelConfig(AkChannelConfiguration ChannelConfiguration, AkChannelConfig& config) { switch (ChannelConfiguration) { case AkChannelConfiguration::Ak_MainMix: config.eConfigType = AK_ChannelConfigType_UseDeviceMain; break; case AkChannelConfiguration::Ak_Passthrough: config.eConfigType = AK_ChannelConfigType_UseDevicePassthrough; break; case AkChannelConfiguration::Ak_LFE: config.SetStandard(AK_SPEAKER_SETUP_0POINT1); break; case AkChannelConfiguration::AK_Audio_Objects: config.SetObject(); break; case AkChannelConfiguration::Ak_1_0: config.SetStandard(AK_SPEAKER_SETUP_MONO); break; case AkChannelConfiguration::Ak_2_0: config.SetStandard(AK_SPEAKER_SETUP_STEREO); break; case AkChannelConfiguration::Ak_2_1: config.SetStandard(AK_SPEAKER_SETUP_2POINT1); break; case AkChannelConfiguration::Ak_3_0: config.SetStandard(AK_SPEAKER_SETUP_3STEREO); break; case AkChannelConfiguration::Ak_3_1: config.SetStandard(AK_SPEAKER_SETUP_3POINT1); break; case AkChannelConfiguration::Ak_4_0: config.SetStandard(AK_SPEAKER_SETUP_4); break; case AkChannelConfiguration::Ak_4_1: config.SetStandard(AK_SPEAKER_SETUP_4POINT1); break; case AkChannelConfiguration::Ak_5_0: config.SetStandard(AK_SPEAKER_SETUP_5); break; case AkChannelConfiguration::Ak_5_1: config.SetStandard(AK_SPEAKER_SETUP_5POINT1); break; case AkChannelConfiguration::Ak_7_1: config.SetStandard(AK_SPEAKER_SETUP_7POINT1); break; case AkChannelConfiguration::Ak_5_1_2: config.SetStandard(AK_SPEAKER_SETUP_DOLBY_5_1_2); break; case AkChannelConfiguration::Ak_7_1_2: config.SetStandard(AK_SPEAKER_SETUP_DOLBY_7_1_2); break; case AkChannelConfiguration::Ak_7_1_4: config.SetStandard(AK_SPEAKER_SETUP_DOLBY_7_1_4); break; case AkChannelConfiguration::Ak_Auro_9_1: config.SetStandard(AK_SPEAKER_SETUP_AURO_9POINT1); break; case AkChannelConfiguration::Ak_Auro_10_1: config.SetStandard(AK_SPEAKER_SETUP_AURO_10POINT1); break; case AkChannelConfiguration::Ak_Auro_11_1: config.SetStandard(AK_SPEAKER_SETUP_AURO_11POINT1); break; case AkChannelConfiguration::Ak_Auro_13_1: config.SetStandard(AK_SPEAKER_SETUP_AURO_13POINT1_751); break; case AkChannelConfiguration::Ak_Ambisonics_1st_order: config.SetAmbisonic(4); break; case AkChannelConfiguration::Ak_Ambisonics_2nd_order: config.SetAmbisonic(9); break; case AkChannelConfiguration::Ak_Ambisonics_3rd_order: config.SetAmbisonic(16); break; case AkChannelConfiguration::Ak_Ambisonics_4th_order: config.SetAmbisonic(25); break; case AkChannelConfiguration::Ak_Ambisonics_5th_order: config.SetAmbisonic(36); break; case AkChannelConfiguration::Ak_Parent: default: config.Clear(); break; } } void FAkAudioDevice::GetChannelConfig(FAkChannelMask SpeakerConfig, AkChannelConfig& config) { config.SetStandard(SpeakerConfig.ChannelMask); } /** * Set spatial audio room * * @param in_GameObjId Wwise Game Object ID * @param in_RoomID ID of the room that the game object is inside. * @return Result from ak sound engine */ AKRESULT FAkAudioDevice::SetInSpatialAudioRoom( const AkGameObjectID in_GameObjId, AkRoomID in_RoomID ) { AKRESULT eResult = AK_Success; #ifdef AK_ENABLE_ROOMS if (m_bSoundEngineInitialized) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; eResult = SpatialAudio->SetGameObjectInRoom(in_GameObjId, in_RoomID); } #endif return eResult; } AKRESULT FAkAudioDevice::SetBusConfig( const FString& in_BusName, AkChannelConfig in_Config ) { AKRESULT eResult = AK_Fail; if (in_BusName.IsEmpty()) { return eResult; } if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; AkUniqueID BusId = GetShortIDFromString(in_BusName); eResult = SoundEngine->SetBusConfig(BusId, in_Config); } return eResult; } AKRESULT FAkAudioDevice::SetPanningRule( AkPanningRule in_ePanningRule ) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetPanningRule(in_ePanningRule); } return eResult; } AkOutputDeviceID FAkAudioDevice::GetOutputID( const FString& in_szShareSet, AkUInt32 in_idDevice ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_INVALID_OUTPUT_DEVICE_ID; return SoundEngine->GetOutputID(TCHAR_TO_AK(*in_szShareSet), in_idDevice); } AKRESULT FAkAudioDevice::ReplaceMainOutput(const AkOutputSettings& MainOutputSettings) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; AKRESULT Result = SoundEngine->ReplaceOutput(MainOutputSettings, 0); SoundEngine->RenderAudio(); return Result; } AKRESULT FAkAudioDevice::GetSpeakerAngles( TArray& out_pfSpeakerAngles, AkReal32& out_fHeightAngle, AkOutputDeviceID in_idOutput ) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; AkUInt32 numSpeakers; // Retrieve the number of speaker and height angle eResult = SoundEngine->GetSpeakerAngles(NULL, numSpeakers, out_fHeightAngle); if (eResult != AK_Success) return eResult; // Retrieve the speaker angles out_pfSpeakerAngles.SetNum(numSpeakers); eResult = SoundEngine->GetSpeakerAngles(out_pfSpeakerAngles.GetData(), numSpeakers, out_fHeightAngle, in_idOutput); } return eResult; } AKRESULT FAkAudioDevice::SetSpeakerAngles( const TArray& in_pfSpeakerAngles, AkReal32 in_fHeightAngle, AkOutputDeviceID in_idOutput ) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetSpeakerAngles(in_pfSpeakerAngles.GetData(), in_pfSpeakerAngles.Num(), in_fHeightAngle, in_idOutput); } return eResult; } AKRESULT FAkAudioDevice::SetGameObjectOutputBusVolume( const UAkComponent* in_pEmitter, const UAkComponent* in_pListener, float in_fControlValue ) { AKRESULT eResult = AK_Success; if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; const AkGameObjectID emitterId = in_pEmitter ? in_pEmitter->GetAkGameObjectID() : DUMMY_GAMEOBJ; const AkGameObjectID listenerId = in_pListener ? in_pListener->GetAkGameObjectID() : DUMMY_GAMEOBJ; eResult = SoundEngine->SetGameObjectOutputBusVolume(emitterId, listenerId, in_fControlValue); } return eResult; } /** * Obtain a pointer to the singleton instance of FAkAudioDevice * * @return Pointer to the singleton instance of FAkAudioDevice */ FAkAudioDevice * FAkAudioDevice::Get() { if (UNLIKELY(m_EngineExiting)) { return nullptr; } if (LIKELY(FAkAudioModule::AkAudioModuleInstance)) { return FAkAudioModule::AkAudioModuleInstance->GetAkAudioDevice(); } else { FAkAudioModule* ModulePtr = FModuleManager::LoadModulePtr(TEXT("AkAudio")); UE_CLOG(!ModulePtr, LogAkAudio, Warning, TEXT("No AkAudio module")); UE_CLOG(FAkAudioModule::AkAudioModuleInstance != ModulePtr, LogAkAudio, Warning, TEXT("AkAudio instance (%p) differs from loaded module (%p)."), FAkAudioModule::AkAudioModuleInstance, ModulePtr); return ModulePtr ? ModulePtr->GetAkAudioDevice() : nullptr; } } /** * Gets the system sample rate * * @return Sample rate */ AkUInt32 FAkAudioDevice::GetSampleRate() { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return 0; return m_bSoundEngineInitialized ? SoundEngine->GetSampleRate() : 0; } /** * Enables/disables offline rendering * * @param bEnable Set to true to enable offline rendering */ AKRESULT FAkAudioDevice::SetOfflineRendering(bool bEnable) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return m_bSoundEngineInitialized ? SoundEngine->SetOfflineRendering(bEnable) : AK_Fail; } /** * Sets the offline rendering frame time in seconds. * * @param FrameTimeInSeconds Frame time in seconds used during offline rendering */ AKRESULT FAkAudioDevice::SetOfflineRenderingFrameTime(AkReal32 FrameTimeInSeconds) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return m_bSoundEngineInitialized ? SoundEngine->SetOfflineRenderingFrameTime(FrameTimeInSeconds) : AK_Fail; } /** * Registers a callback used for retrieving audio samples. * * @param Callback Capture callback function to register * @param OutputId The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID * @param Cookie Callback cookie that will be sent to the callback function along with additional information */ AKRESULT FAkAudioDevice::RegisterCaptureCallback(AkCaptureCallbackFunc Callback, AkOutputDeviceID OutputId /*= AK_INVALID_OUTPUT_DEVICE_ID*/, void* Cookie /*= nullptr*/) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return m_bSoundEngineInitialized ? SoundEngine->RegisterCaptureCallback(Callback, OutputId, Cookie) : AK_Fail; } /** * Unregisters a callback used for retrieving audio samples. * * @param Callback Capture callback function to register * @param OutputId The audio device specific id, return by AK::SoundEngine::AddOutput or AK::SoundEngine::GetOutputID * @param Cookie Callback cookie that will be sent to the callback function along with additional information */ AKRESULT FAkAudioDevice::UnregisterCaptureCallback(AkCaptureCallbackFunc Callback, AkOutputDeviceID OutputId /*= AK_INVALID_OUTPUT_DEVICE_ID*/, void* Cookie /*= nullptr*/) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return m_bSoundEngineInitialized ? SoundEngine->UnregisterCaptureCallback(Callback, OutputId, Cookie) : AK_Fail; } /** * Stop all audio associated with a game object * * @param in_GameObjID ID of the game object */ void FAkAudioDevice::StopGameObject( UAkComponent * in_pComponent ) { AkGameObjectID gameObjId = DUMMY_GAMEOBJ; if ( in_pComponent ) { gameObjId = in_pComponent->GetAkGameObjectID(); } if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; SoundEngine->StopAll( gameObjId ); } } /** * Stop all audio associated with a playing ID * * @param in_playingID Playing ID to stop * @param in_uTransitionDuration Fade duration * @param in_eFadeCurve Curve type to be used for the transition */ void FAkAudioDevice::StopPlayingID( AkPlayingID in_playingID, AkTimeMs in_uTransitionDuration /*= 0*/, AkCurveInterpolation in_eFadeCurve /*= AkCurveInterpolation_Linear*/) { if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; SoundEngine->ExecuteActionOnPlayingID(AK::SoundEngine::AkActionOnEventType_Stop, in_playingID, in_uTransitionDuration, in_eFadeCurve ); } } /** * Register an ak audio component with ak sound engine * * @param in_pComponent Pointer to the component to register */ void FAkAudioDevice::RegisterComponent( UAkComponent * in_pComponent ) { if (m_bSoundEngineInitialized && in_pComponent) { FString WwiseGameObjectName = TEXT(""); in_pComponent->GetAkGameObjectName(WwiseGameObjectName); const AkGameObjectID gameObjId = in_pComponent->GetAkGameObjectID(); FAkAudioDevice_Helpers::RegisterGameObject(gameObjId, WwiseGameObjectName); if (CallbackManager != nullptr) CallbackManager->RegisterGameObject(gameObjId); } } /** * Register a game object with ak sound engine * * @param GameObjectID ID of the game object to register */ void FAkAudioDevice::RegisterComponent(AkGameObjectID GameObjectID) { if (m_bSoundEngineInitialized && GameObjectID) { FAkAudioDevice_Helpers::RegisterGameObject(GameObjectID, ""); if (CallbackManager != nullptr) CallbackManager->RegisterGameObject(GameObjectID); } } /** * Unregister an ak audio component with ak sound engine * * @param in_pComponent Pointer to the component to unregister */ void FAkAudioDevice::UnregisterComponent( UAkComponent * in_pComponent ) { if (m_bSoundEngineInitialized && in_pComponent) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (LIKELY(SoundEngine)) { const AkGameObjectID gameObjId = in_pComponent->GetAkGameObjectID(); SoundEngine->UnregisterGameObj(gameObjId); if (CallbackManager != nullptr) { CallbackManager->UnregisterGameObject(gameObjId); } } } if (m_defaultListeners.Contains(in_pComponent)) { RemoveDefaultListener(in_pComponent); } check(!m_defaultListeners.Contains(in_pComponent)); if (m_SpatialAudioListener == in_pComponent) m_SpatialAudioListener = nullptr; } void FAkAudioDevice::UnregisterComponent( AkGameObjectID GameObjectId ) { if (m_bSoundEngineInitialized && GameObjectId) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (LIKELY(SoundEngine)) { SoundEngine->UnregisterGameObj(GameObjectId); } if (CallbackManager != nullptr) { CallbackManager->UnregisterGameObject(GameObjectId); } } } AKRESULT FAkAudioDevice::SetGeometry(AkGeometrySetID GeometrySetID, const AkGeometryParams& Params) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; eResult = SpatialAudio->SetGeometry(GeometrySetID, Params); } return eResult; } AKRESULT FAkAudioDevice::SetGeometryInstance(AkGeometryInstanceID GeometryInstanceID, const AkGeometryInstanceParams& Params) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; eResult = SpatialAudio->SetGeometryInstance(GeometryInstanceID, Params); } return eResult; } AKRESULT FAkAudioDevice::RemoveGeometrySet(AkGeometrySetID GeometrySetID) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; eResult = SpatialAudio->RemoveGeometry(GeometrySetID); } return eResult; } AKRESULT FAkAudioDevice::RemoveGeometryInstance(AkGeometryInstanceID GeometryInstanceID) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; eResult = SpatialAudio->RemoveGeometryInstance(GeometryInstanceID); } return eResult; } AKRESULT FAkAudioDevice::SetEarlyReflectionsAuxBus(UAkComponent* in_pComponent, const AkUInt32 AuxBusID) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized && in_pComponent) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; const AkGameObjectID gameObjId = in_pComponent->GetAkGameObjectID(); eResult = SpatialAudio->SetEarlyReflectionsAuxSend(gameObjId, AuxBusID); } return eResult; } AKRESULT FAkAudioDevice::SetEarlyReflectionsVolume(UAkComponent* in_pComponent, float in_fSendVolume) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized && in_pComponent) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; const AkGameObjectID gameObjId = in_pComponent->GetAkGameObjectID(); eResult = SpatialAudio->SetEarlyReflectionsVolume(gameObjId, in_fSendVolume); } return eResult; } AKRESULT FAkAudioDevice::SetReflectionsOrder(int Order, bool RefreshPaths) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; eResult = SpatialAudio->SetReflectionsOrder(Order, RefreshPaths); } return eResult; } AKRESULT FAkAudioDevice::SetMultipleObstructionAndOcclusion(AkGameObjectID in_Object, AkGameObjectID in_listener, AkObstructionOcclusionValues* ObstructionAndOcclusionValues, AkUInt32 in_uNumObstructionAndOcclusion) { AKRESULT eResult = AK_Fail; if(m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetMultipleObstructionAndOcclusion(in_Object, in_listener, ObstructionAndOcclusionValues, in_uNumObstructionAndOcclusion); } return eResult; } AKRESULT FAkAudioDevice::SetObjectObstructionAndOcclusion(AkGameObjectID in_Object, AkGameObjectID in_listener, AkReal32 Obstruction, AkReal32 Occlusion) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetObjectObstructionAndOcclusion(in_Object, in_listener, Obstruction, Occlusion); } return eResult; } AKRESULT FAkAudioDevice::SetPortalObstructionAndOcclusion(const UAkPortalComponent* in_pPortal, float in_fObstructionValue, float in_fOcclusionValue) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized && in_pPortal) { auto World = in_pPortal->GetWorld(); if (World && ShouldNotifySoundEngine(World->WorldType)) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; const AkPortalID portalID = in_pPortal->GetPortalID(); eResult = SpatialAudio->SetPortalObstructionAndOcclusion(portalID, in_fObstructionValue, in_fOcclusionValue); } } return eResult; } AKRESULT FAkAudioDevice::SetGameObjectToPortalObstruction(const UAkComponent* in_pComponent, const UAkPortalComponent* in_pPortal, float in_fObstructionValue) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized && in_pComponent && in_pPortal) { auto World = in_pComponent->GetWorld(); if (World && ShouldNotifySoundEngine(World->WorldType)) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; const AkGameObjectID gameObjId = in_pComponent->GetAkGameObjectID(); const AkPortalID portalID = in_pPortal->GetPortalID(); eResult = SpatialAudio->SetGameObjectToPortalObstruction(gameObjId, portalID, in_fObstructionValue); } } return eResult; } AKRESULT FAkAudioDevice::SetPortalToPortalObstruction(const UAkPortalComponent* in_pPortal0, const UAkPortalComponent* in_pPortal1, float in_fObstructionValue) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized && in_pPortal0 && in_pPortal1) { auto World = in_pPortal0->GetWorld(); if (World && ShouldNotifySoundEngine(World->WorldType)) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; const AkPortalID portalID0 = in_pPortal0->GetPortalID(); const AkPortalID portalID1 = in_pPortal1->GetPortalID(); eResult = SpatialAudio->SetPortalToPortalObstruction(portalID0, portalID1, in_fObstructionValue); } } return eResult; } void FAkAudioDevice::UpdateDefaultActiveListeners() { if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; auto NumDefaultListeners = m_defaultListeners.Num(); auto pListenerIds = (AkGameObjectID*)alloca(NumDefaultListeners * sizeof(AkGameObjectID)); int index = 0; for (auto DefaultListenerIter = m_defaultListeners.CreateConstIterator(); DefaultListenerIter; ++DefaultListenerIter) pListenerIds[index++] = (*DefaultListenerIter)->GetAkGameObjectID(); if (NumDefaultListeners > 0) { SoundEngine->SetDefaultListeners(pListenerIds, NumDefaultListeners); } } } AKRESULT FAkAudioDevice::SetPosition(UAkComponent* in_akComponent, const AkSoundPosition& in_SoundPosition) { if (m_bSoundEngineInitialized) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; return SoundEngine->SetPosition(in_akComponent->GetAkGameObjectID(), in_SoundPosition); } return AK_Fail; } AKRESULT FAkAudioDevice::AddRoom(UAkRoomComponent* in_pRoom, const AkRoomParams& in_RoomParams) { if (ShouldNotifySoundEngine(in_pRoom->GetWorld()->WorldType)) { AKRESULT result = AK_Fail; if (m_bSoundEngineInitialized) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; UAkLateReverbComponent* ReverbComp = in_pRoom->GetReverbComponent(); UE_CLOG(UNLIKELY(ReverbComp && ReverbComp->bEnable && in_RoomParams.ReverbAuxBus == AK_INVALID_AUX_ID), LogAkAudio, Warning, TEXT("Enabled Late Reverb component for room %s without an assigned Late Reverb Aux Bus"), *in_pRoom->GetRoomName()); result = SpatialAudio->SetRoom(in_pRoom->GetRoomID(), in_RoomParams, TCHAR_TO_ANSI(*in_pRoom->GetRoomName())); if (result == AK_Success) { IndexRoom(in_pRoom); PortalsNeedRoomUpdate(in_pRoom->GetWorld()); } } return result; } IndexRoom(in_pRoom); PortalsNeedRoomUpdate(in_pRoom->GetWorld()); return AK_Success; } AKRESULT FAkAudioDevice::UpdateRoom(UAkRoomComponent* in_pRoom, const AkRoomParams& in_RoomParams) { if (ShouldNotifySoundEngine(in_pRoom->GetWorld()->WorldType)) { AKRESULT result = AK_Fail; if (m_bSoundEngineInitialized) { check(in_pRoom->HasBeenRegisteredWithWwise()); auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; check(in_pRoom->HasBeenRegisteredWithWwise()); result = SpatialAudio->SetRoom(in_pRoom->GetRoomID(), in_RoomParams, TCHAR_TO_ANSI(*in_pRoom->GetRoomName())); if (result == AK_Success) PortalsNeedRoomUpdate(in_pRoom->GetWorld()); } return result; } PortalsNeedRoomUpdate(in_pRoom->GetWorld()); return AK_Success; } AKRESULT FAkAudioDevice::RemoveRoom(UAkRoomComponent* in_pRoom) { if (ShouldNotifySoundEngine(in_pRoom->GetWorld()->WorldType)) { AKRESULT result = AK_Fail; if (m_bSoundEngineInitialized) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; result = SpatialAudio->RemoveRoom(in_pRoom->GetRoomID()); if (result == AK_Success) { UnindexRoom(in_pRoom); PortalsNeedRoomUpdate(in_pRoom->GetWorld()); } } return result; } UnindexRoom(in_pRoom); PortalsNeedRoomUpdate(in_pRoom->GetWorld()); return AK_Success; } AKRESULT FAkAudioDevice::SetGameObjectRadius(UAkComponent* in_akComponent, float in_outerRadius, float in_innerRadius) { if (!m_bSoundEngineInitialized) return AK_Fail; auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; return SpatialAudio->SetGameObjectRadius(AkGameObjectID(in_akComponent), in_outerRadius, in_innerRadius); } AKRESULT FAkAudioDevice::SetImageSource(AAkSpotReflector* in_pSpotReflector, const AkImageSourceSettings& in_ImageSourceInfo, AkUniqueID in_AuxBusID, UAkComponent* in_AkComponent) { if (m_bSoundEngineInitialized) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; return SpatialAudio->SetImageSource(in_pSpotReflector->GetImageSourceID(), in_ImageSourceInfo, TCHAR_TO_ANSI(*in_pSpotReflector->GetSpotReflectorName()), in_AuxBusID, in_AkComponent->GetAkGameObjectID()); } return AK_Fail; } AKRESULT FAkAudioDevice::RemoveImageSource(AAkSpotReflector* in_pSpotReflector, AkUniqueID in_AuxBusID, UAkComponent* in_AkComponent) { if (m_bSoundEngineInitialized) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; return SpatialAudio->RemoveImageSource(in_pSpotReflector->GetImageSourceID(), in_AuxBusID, in_AkComponent->GetAkGameObjectID()); } return AK_Fail; } AKRESULT FAkAudioDevice::ClearImageSources(AkUniqueID in_AuxBusID, UAkComponent* in_AkComponent) { if (m_bSoundEngineInitialized) { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return AK_NotInitialized; return SpatialAudio->ClearImageSources(in_AuxBusID, in_AkComponent == NULL ? AK_INVALID_GAME_OBJECT : in_AkComponent->GetAkGameObjectID()); } return AK_Fail; } void FAkAudioDevice::SetListeners(UAkComponent* in_pEmitter, const TArray& in_listenerSet) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; check(!in_pEmitter->UseDefaultListeners()); auto NumListeners = in_listenerSet.Num(); auto pListenerIds = (AkGameObjectID*)alloca(NumListeners * sizeof(AkGameObjectID)); int index = 0; for (const auto& Listener : in_listenerSet) pListenerIds[index++] = Listener->GetAkGameObjectID(); SoundEngine->SetListeners(in_pEmitter->GetAkGameObjectID(), pListenerIds, NumListeners); } bool FAkAudioDevice::SetSpatialAudioListener(UAkComponent* in_pListener) { #if WITH_EDITOR if (in_pListener == EditorListener) { return false; } #endif m_SpatialAudioListener = in_pListener; auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return false; SpatialAudio->RegisterListener((AkGameObjectID)m_SpatialAudioListener); return true; } UAkComponent* FAkAudioDevice::GetSpatialAudioListener() const { return m_SpatialAudioListener; } UAkComponent* FAkAudioDevice::GetAkComponent(class USceneComponent* AttachToComponent, FName AttachPointName, const FVector * Location, EAttachLocation::Type LocationType) { bool ComponentCreated; return GetAkComponent(AttachToComponent, AttachPointName, Location, LocationType, ComponentCreated); } UAkComponent* FAkAudioDevice::GetAkComponent( class USceneComponent* AttachToComponent, FName AttachPointName, const FVector * Location, EAttachLocation::Type LocationType, bool& ComponentCreated ) { if (!AttachToComponent) { return NULL; } UAkComponent* AkComponent = NULL; FAttachmentTransformRules AttachRules = FAttachmentTransformRules::KeepRelativeTransform; auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return nullptr; if( GEngine && SoundEngine->IsInitialized()) { AActor * Actor = AttachToComponent->GetOwner(); if( Actor ) { if( !IsValid(Actor) ) { // Avoid creating component if we're trying to play a sound on an already destroyed actor. return NULL; } TArray AkComponents; Actor->GetComponents(AkComponents); for ( int32 CompIdx = 0; CompIdx < AkComponents.Num(); CompIdx++ ) { UAkComponent* pCompI = AkComponents[CompIdx]; if ( pCompI && pCompI->IsRegistered() ) { if ( AttachToComponent == pCompI ) { return pCompI; } if ( AttachToComponent != pCompI->GetAttachParent() || AttachPointName != pCompI->GetAttachSocketName() ) { continue; } // If a location is requested, try to match location. if ( Location ) { if (LocationType == EAttachLocation::KeepWorldPosition) { AttachRules = FAttachmentTransformRules::KeepWorldTransform; if ( !FVector::PointsAreSame(*Location, pCompI->GetComponentLocation()) ) continue; } else { AttachRules = FAttachmentTransformRules::KeepRelativeTransform; auto RelLoc = pCompI->GetRelativeLocation(); if ( !FVector::PointsAreSame(*Location, RelLoc) ) continue; } } // AkComponent found which exactly matches the attachment: reuse it. ComponentCreated = false; return pCompI; } } } else { // Try to find if there is an AkComponent attached to AttachToComponent (will be the case if AttachToComponent has no owner) const TArray AttachChildren = AttachToComponent->GetAttachChildren(); for(int32 CompIdx = 0; CompIdx < AttachChildren.Num(); CompIdx++) { UAkComponent* pCompI = Cast(AttachChildren[CompIdx]); if ( pCompI && pCompI->IsRegistered() ) { // There is an associated AkComponent to AttachToComponent, no need to add another one. ComponentCreated = false; return pCompI; } } } if ( AkComponent == NULL ) { if( Actor ) { AkComponent = NewObject(Actor); } else { AkComponent = NewObject(); } } ComponentCreated = true; check( AkComponent ); if (Location) { if (LocationType == EAttachLocation::KeepWorldPosition) { AttachRules = FAttachmentTransformRules::KeepWorldTransform; AkComponent->SetWorldLocation(*Location); } else { AttachRules = FAttachmentTransformRules::KeepRelativeTransform; AkComponent->SetRelativeLocation(*Location); } } AkComponent->RegisterComponentWithWorld(AttachToComponent->GetWorld()); AkComponent->AttachToComponent(AttachToComponent, AttachRules, AttachPointName); } return( AkComponent ); } /** * Cancel the callback cookie for a dispatched event * * @param in_cookie The cookie to cancel */ void FAkAudioDevice::CancelEventCallbackCookie(void* in_cookie) { if (m_bSoundEngineInitialized) { CallbackManager->CancelEventCallback(in_cookie); } } /** * Cancel the callback cookie for a dispatched event * * @param in_cookie The cookie to cancel */ void FAkAudioDevice::CancelEventCallbackDelegate(const FOnAkPostEventCallback& in_Delegate) { if (m_bSoundEngineInitialized) { CallbackManager->CancelEventCallback(in_Delegate); } } AKRESULT FAkAudioDevice::SetAttenuationScalingFactor(AActor* Actor, float ScalingFactor) { AKRESULT eResult = AK_Fail; if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; AkGameObjectID GameObjID = DUMMY_GAMEOBJ; eResult = GetGameObjectID( Actor, GameObjID ); if( eResult == AK_Success ) { eResult = SoundEngine->SetScalingFactor(GameObjID, ScalingFactor); } } return eResult; } AKRESULT FAkAudioDevice::SetAttenuationScalingFactor(UAkComponent* AkComponent, float ScalingFactor) { AKRESULT eResult = AK_Fail; if ( m_bSoundEngineInitialized && AkComponent) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetScalingFactor(AkComponent->GetAkGameObjectID(), ScalingFactor); } return eResult; } AKRESULT FAkAudioDevice::SetDistanceProbe(UAkComponent* Listener, UAkComponent* DistanceProbe) { AKRESULT eResult = AK_Fail; if (m_bSoundEngineInitialized && Listener) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; eResult = SoundEngine->SetDistanceProbe(Listener->GetAkGameObjectID(), DistanceProbe != nullptr ? DistanceProbe->GetAkGameObjectID() : AK_INVALID_GAME_OBJECT ); } return eResult; } #if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) AkErrorMessageTranslator* FAkAudioDevice::m_UnrealErrorTranslator; #endif AKRESULT FAkAudioDevice::RegisterGameObject(AkGameObjectID GameObjectID, const FString& Name) { return FAkAudioDevice_Helpers::RegisterGameObject(GameObjectID, Name); } bool FAkAudioDevice::EnsureInitialized() { static bool bPermanentInitializationFailure = false; static bool bLogWwiseVersionOnce = true; if (LIKELY(m_bSoundEngineInitialized)) { return true; } if (UNLIKELY(bPermanentInitializationFailure)) { return false; } SCOPED_AKAUDIO_EVENT_2(TEXT("FAkAudioDevice::EnsureInitialized")); UE_CLOG(bLogWwiseVersionOnce, LogAkAudio, Log, TEXT("Wwise(R) SDK Version %d.%d.%d Build %d [%s]. Copyright (c) 2006-%d Audiokinetic Inc."), AK_WWISESDK_VERSION_MAJOR, AK_WWISESDK_VERSION_MINOR, AK_WWISESDK_VERSION_SUBMINOR, AK_WWISESDK_VERSION_BUILD, TEXT(WWISE_CONFIGURATION_DIR), AK_WWISESDK_VERSION_MAJOR); bLogWwiseVersionOnce = false; auto* ResourceLoader = FWwiseResourceLoader::Get(); if (UNLIKELY(!ResourceLoader)) { UE_LOG(LogAkAudio, Error, TEXT("Wwise Initialization Error: No ResourceLoader module")); bPermanentInitializationFailure = true; return false; } // We don't want sound in those cases. if (AK_USE_NULL_SOUNDENGINE) { UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: Using the null SoundEngine.")); UE_LOG(LogWwiseHints, Log, TEXT("Usage of the null SoundEngine is determined during UBT. Check UBT logs for more information.")); bPermanentInitializationFailure = true; ResourceLoader->Disable(); return false; } if (FParse::Param(FCommandLine::Get(), TEXT("nosound"))) { UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: \"nosound\" command line parameter.")); bPermanentInitializationFailure = true; ResourceLoader->Disable(); return false; } if (FApp::IsBenchmarking()) { UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: App is benchmarking.")); bPermanentInitializationFailure = true; ResourceLoader->Disable(); return false; } if (IsRunningDedicatedServer()) { UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: Running a dedicated server.")); bPermanentInitializationFailure = true; ResourceLoader->Disable(); return false; } if (IsRunningCommandlet()) { UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: Running a commandlet.")); bPermanentInitializationFailure = true; ResourceLoader->Disable(); return false; } const UAkSettings* AkSettings = GetDefault(); if (UNLIKELY(!AkSettings)) { UE_LOG(LogAkAudio, Error, TEXT("Wwise Initialization Error: No default settings.")); bPermanentInitializationFailure = true; ResourceLoader->Disable(); return false; } if (!AkSettings->bWwiseSoundEngineEnabled) { UE_LOG(LogAkAudio, Display, TEXT("Wwise SoundEngine is disabled: Audio Routing is set to Enable Unreal Audio only.")); bPermanentInitializationFailure = true; ResourceLoader->Disable(); return false; } auto* FileHandlerModule = IWwiseFileHandlerModule::GetModule(); if (UNLIKELY(!FileHandlerModule)) { UE_LOG(LogAkAudio, Error, TEXT("Wwise Initialization Error: No file handling module")); bPermanentInitializationFailure = true; ResourceLoader->Disable(); return false; } auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) { UE_LOG(LogAkAudio, Error, TEXT("Wwise Initialization Error: No Sound Engine")); bPermanentInitializationFailure = true; ResourceLoader->Disable(); return false; } IOHook = FileHandlerModule->InstantiateIOHook(); if (UNLIKELY(!IOHook)) { UE_LOG(LogAkAudio, Error, TEXT("Wwise Initialization Error: No IO Hook")); bPermanentInitializationFailure = true; ResourceLoader->Disable(); return false; } // From this point on, if we get an error, we can try initializing later if (UNLIKELY(!FAkSoundEngineInitialization::Initialize(IOHook))) { UE_LOG(LogAkAudio, Display, TEXT("Wwise Initialization Error.")); FAkSoundEngineInitialization::Finalize(IOHook); delete IOHook; IOHook = nullptr; ResourceLoader->Disable(); return false; } UE_LOG(LogAkAudio, Log, TEXT("Wwise SoundEngine successfully initialized.")); SetLocalOutput(); // Init dummy game object SoundEngine->RegisterGameObj(DUMMY_GAMEOBJ, "Unreal Global"); #if WITH_EDITOR if (!IsRunningGame()) { AkGameObjectID tempID = DUMMY_GAMEOBJ; SoundEngine->SetListeners(DUMMY_GAMEOBJ, &tempID, 1); } #endif m_bSoundEngineInitialized = true; CallbackInfoPool = new AkCallbackInfoPool; // Go get the max number of Aux busses MaxAuxBus = AkSettings->MaxSimultaneousReverbVolumes; //TUniquePtr CallbackManager = new FAkComponentCallbackManager(); SetCurrentAudioCulture(GetDefaultLanguage()); UE_LOG(LogAkAudio, Log, TEXT("Initialization complete.")); return CallbackManager != nullptr; } void FAkAudioDevice::SetLocalOutput() { auto* Monitor = IWwiseMonitorAPI::Get(); if (UNLIKELY(!Monitor)) { return; } Monitor->ResetTranslator(); #if WITH_EDITORONLY_DATA && !defined(AK_OPTIMIZED) const UAkSettingsPerUser* AkSettingsPerUser = GetDefault(); if (AkSettingsPerUser->WaapiTranslatorTimeout > 0) { #if AK_SUPPORT_WAAPI Monitor->SetupDefaultWAAPIErrorTranslator(AkSettingsPerUser->WaapiIPAddress, AkSettingsPerUser->WaapiPort, AkSettingsPerUser->WaapiTranslatorTimeout); #endif //AK_SUPPORT_WAAPI } if (!m_UnrealErrorTranslator) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) { return; } m_UnrealErrorTranslator = SoundEngine->NewErrorMessageTranslator(&GetInfoErrorMessageTranslatorFunction); } if (m_UnrealErrorTranslator) { Monitor->AddTranslator(m_UnrealErrorTranslator); } #endif } void FAkAudioDevice::AddDefaultListener(UAkComponent* in_pListener) { bool bAlreadyInSet; m_defaultListeners.Add(in_pListener, &bAlreadyInSet); if (!bAlreadyInSet) { for (TObjectIterator Emitter; Emitter; ++Emitter) { Emitter->OnDefaultListenerAdded(in_pListener); } in_pListener->IsListener = true; in_pListener->IsDefaultListener = true; UpdateDefaultActiveListeners(); if (m_SpatialAudioListener == nullptr) SetSpatialAudioListener(in_pListener); } } void FAkAudioDevice::RemoveDefaultListener(UAkComponent* in_pListener) { for (TObjectIterator Emitter; Emitter; ++Emitter) { Emitter->OnListenerUnregistered(in_pListener); } m_defaultListeners.Remove(in_pListener); in_pListener->IsListener = false; in_pListener->IsDefaultListener = false; UpdateDefaultActiveListeners(); // We are setting Aux Sends with the SpatialAudio API, and that requires a Spatial Audio listener. // When running dedicated server, Unreal creates a camera manager (default listener 1 gets set as spatial audio listener), then another one (default listener 2), and then destroys the first. This leaves us with a default listener, but no spatial audio listener. This fix targets that issue. if (m_SpatialAudioListener == in_pListener ) { // Unregister the Spatial Audio Listener if its game object is unregistered auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (LIKELY(SpatialAudio)) { SpatialAudio->UnregisterListener(m_SpatialAudioListener->GetAkGameObjectID()); } m_SpatialAudioListener = nullptr; if (m_defaultListeners.Num() > 0) { for (auto listener : m_defaultListeners) { if (SetSpatialAudioListener(m_defaultListeners.Array()[0])) { break; } } } } } void FAkAudioDevice::OnActorSpawned(AActor* SpawnedActor) { APlayerCameraManager* AsPlayerCameraManager = Cast(SpawnedActor); if (AsPlayerCameraManager && AsPlayerCameraManager->GetWorld()->AllowAudioPlayback()) { APlayerController* CameraOwner = Cast(AsPlayerCameraManager->GetOwner()); if (CameraOwner && CameraOwner->IsLocalPlayerController()) { UAkComponent* pAkComponent = NewObject(SpawnedActor); if (pAkComponent != nullptr) { pAkComponent->RegisterComponentWithWorld(SpawnedActor->GetWorld()); pAkComponent->AttachToComponent(SpawnedActor->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform, FName()); AddDefaultListener(pAkComponent); } } } } FString FAkAudioDevice::GetBasePath() { return WwiseUnrealHelper::GetSoundBankDirectory(); } /** * Allocates memory from permanent pool. This memory will NEVER be freed. * * @param Size Size of allocation. * * @return pointer to a chunk of memory with size Size */ void* FAkAudioDevice::AllocatePermanentMemory( int32 Size, bool& AllocatedInPool ) { return 0; } AKRESULT FAkAudioDevice::GetGameObjectID( AActor * in_pActor, AkGameObjectID& io_GameObject ) { if ( IsValid(in_pActor) ) { UAkComponent * pComponent = GetAkComponent( in_pActor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset ); if ( pComponent ) { io_GameObject = pComponent->GetAkGameObjectID(); return AK_Success; } else return AK_Fail; } // we do not modify io_GameObject, letting it to the specified default value. return AK_Success; } AKRESULT FAkAudioDevice::GetGameObjectID( AActor * in_pActor, AkGameObjectID& io_GameObject, bool in_bStopWhenOwnerDestroyed ) { if ( IsValid(in_pActor) ) { UAkComponent * pComponent = GetAkComponent( in_pActor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset ); if ( pComponent ) { pComponent->StopWhenOwnerDestroyed = in_bStopWhenOwnerDestroyed; io_GameObject = pComponent->GetAkGameObjectID(); return AK_Success; } else return AK_Fail; } // we do not modify io_GameObject, letting it to the specified default value. return AK_Success; } void FAkAudioDevice::Suspend(bool in_bRenderAnyway /* = false */) { if (!m_isSuspended) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; SoundEngine->Suspend(in_bRenderAnyway); m_isSuspended = true; } } void FAkAudioDevice::WakeupFromSuspend() { if (m_isSuspended) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; SoundEngine->WakeupFromSuspend(); m_isSuspended = false; } } void FAkAudioDevice::StartOutputCapture(const FString& Filename) { if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; SoundEngine->StartOutputCapture(TCHAR_TO_AK(*Filename)); } } void FAkAudioDevice::StopOutputCapture() { if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; SoundEngine->StopOutputCapture(); } } void FAkAudioDevice::StartProfilerCapture(const FString& Filename) { if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; SoundEngine->StartProfilerCapture(TCHAR_TO_AK(*Filename)); } } void FAkAudioDevice::AddOutputCaptureMarker(const FString& MarkerText) { if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; SoundEngine->AddOutputCaptureMarker(TCHAR_TO_ANSI(*MarkerText)); } } void FAkAudioDevice::StopProfilerCapture() { if ( m_bSoundEngineInitialized ) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; SoundEngine->StopProfilerCapture(); } } AKRESULT FAkAudioDevice::RegisterPluginDLL(const FString& in_DllName, const FString& in_DllPath) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return AK_NotInitialized; AkOSChar* szPath = nullptr; if (!in_DllPath.IsEmpty()) { auto Length = in_DllPath.Len() + 1; szPath = new AkOSChar[Length]; AKPLATFORM::SafeStrCpy(szPath, TCHAR_TO_AK(*in_DllPath), Length); } AKRESULT eResult = SoundEngine->RegisterPluginDLL(TCHAR_TO_AK(*in_DllName), szPath); delete[] szPath; return eResult; } // end FAkAudioDevice::SetCurrentAudioCultureAsyncTask::SetCurrentAudioCultureAsyncTask(FWwiseLanguageCookedData NewLanguage, FSetCurrentAudioCultureAction* LatentAction) : Language(NewLanguage) , SetAudioCultureLatentAction(LatentAction) { CompletionActionType = CompletionType::LatentAction; LatentActionValidityToken = MakeShared(); SetAudioCultureLatentAction->ValidityToken = LatentActionValidityToken; } FAkAudioDevice::SetCurrentAudioCultureAsyncTask::SetCurrentAudioCultureAsyncTask(FWwiseLanguageCookedData NewLanguage, const FOnSetCurrentAudioCultureCompleted& CompletedCallback) : Language(NewLanguage) , SetAudioCultureCompletedCallback(CompletedCallback) { CompletionActionType = CompletionType::Callback; } bool FAkAudioDevice::SetCurrentAudioCultureAsyncTask::Start() { UE_LOG(LogAkAudio, Verbose, TEXT("Switching Wwise language to '%s'"), *Language.GetLanguageName().ToString()); auto* StreamMgr = IWwiseStreamMgrAPI::Get(); if (UNLIKELY(!StreamMgr)) { return false; } StreamMgr->SetCurrentLanguage(TCHAR_TO_AK(*Language.GetLanguageName().ToString())); AsyncTask(ENamedThreads::AnyNormalThreadNormalTask, [this]() { auto* ResourceLoader = FWwiseResourceLoader::Get(); if (UNLIKELY(!ResourceLoader)) { UE_LOG(LogAkAudio, Error, TEXT("SetCurrentAudioCultureAsync: Could not get resource loader, cannot change language.")); Succeeded = false; IsDone = true; return; } ResourceLoader->SetLanguage(Language, EWwiseReloadLanguage::Immediate); IsDone = true; Succeeded = true; }); return true; } void FAkAudioDevice::SetCurrentAudioCultureAsyncTask::Update() { if (IsDone) { switch (CompletionActionType) { case CompletionType::Callback: SetAudioCultureCompletedCallback.ExecuteIfBound(Succeeded); break; case CompletionType::LatentAction: if (LatentActionValidityToken->bValid && SetAudioCultureLatentAction) { SetAudioCultureLatentAction->ActionDone = true; } break; } } } void FAkAudioDevice::AddPlayingID(uint32 EventID, uint32 PlayingID, EAkAudioContext AudioContext) { FScopeLock Lock(&EventToPlayingIDMapCriticalSection); auto& PlayingIDArray = EventToPlayingIDMap.FindOrAdd(EventID); PlayingIDArray.Add(PlayingID); PlayingIDToAudioContextMap.Add(PlayingID, AudioContext); } bool FAkAudioDevice::IsPlayingIDActive(uint32 EventID, uint32 PlayingID) { FScopeLock Lock(&EventToPlayingIDMapCriticalSection); auto* PlayingIDArray = EventToPlayingIDMap.Find(EventID); if (PlayingIDArray && PlayingIDArray->Contains(PlayingID)) { return true; } return false; } bool FAkAudioDevice::IsEventIDActive(uint32 EventID) { FScopeLock Lock(&EventToPlayingIDMapCriticalSection); return EventToPlayingIDMap.Contains(EventID); } void FAkAudioDevice::RemovePlayingID(uint32 EventID, uint32 PlayingID) { FScopeLock Lock(&EventToPlayingIDMapCriticalSection); auto* PlayingIDArray = EventToPlayingIDMap.Find(EventID); if (PlayingIDArray) { PlayingIDArray->Remove(PlayingID); if (PlayingIDArray->Num() == 0) { EventToPlayingIDMap.Remove(EventID); PlayingIDToAudioContextMap.Remove(PlayingID); } } } void FAkAudioDevice::StopEventID(uint32 EventID) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; FScopeLock Lock(&EventToPlayingIDMapCriticalSection); auto* PlayingIDs = EventToPlayingIDMap.Find(EventID); if (PlayingIDs) { for (auto pID : *PlayingIDs) { StopPlayingID(pID); } SoundEngine->RenderAudio(); } } FOnSwitchValueLoaded& FAkAudioDevice::GetOnSwitchValueLoaded(uint32 SwitchID) { return OnSwitchValueLoadedMap.FindOrAdd(SwitchID); } void FAkAudioDevice::BroadcastOnSwitchValueLoaded(UAkGroupValue* GroupValue) { FOnSwitchValueLoaded* EventToBroadcast = OnSwitchValueLoadedMap.Find(GroupValue->GetShortID()); if (EventToBroadcast) { EventToBroadcast->Broadcast(GroupValue); } } void FAkAudioDevice::AddPortalConnectionToOutdoors(const UWorld* in_world, UAkPortalComponent* in_pPortal) { if (in_world == nullptr) { return; } OutdoorsConnectedPortals.FindOrAdd(in_world).Add(in_pPortal->GetPortalID(), in_pPortal); } void FAkAudioDevice::RemovePortalConnectionToOutdoors(const UWorld* in_world, AkPortalID in_portalID) { if (in_world == nullptr) { return; } auto pPortals = OutdoorsConnectedPortals.Find(in_world); if (pPortals == nullptr) { return; } pPortals->Remove(in_portalID); } void FAkAudioDevice::GetObsOccServicePortalMap(const UAkRoomComponent* InRoom, const UWorld* InWorld, AkObstructionAndOcclusionService::PortalMap& OutPortalMap) const { PortalComponentMap ConnectedPortals; if (InRoom != nullptr) { ConnectedPortals = InRoom->GetConnectedPortals(); } else { auto Portals = OutdoorsConnectedPortals.Find(InWorld); if (Portals != nullptr) { ConnectedPortals = *Portals; } } for (auto& Portal : ConnectedPortals) { AkObstructionAndOcclusionService::FPortalInfo PortalInfo(Portal.Value->Bounds.GetBox().GetCenter(), Portal.Value->ObstructionRefreshInterval != 0.f); OutPortalMap.Add(Portal.Key, PortalInfo); } }