/******************************************************************************* The content of this file includes portions of the proprietary AUDIOKINETIC Wwise Technology released in source code form as part of the game integration package. The content of this file may not be used without valid licenses to the AUDIOKINETIC Wwise Technology. Note that the use of the game engine is subject to the Unreal(R) Engine End User License Agreement at https://www.unrealengine.com/en-US/eula/unreal License Usage Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use this file in accordance with the end user license agreement provided with the software or, alternatively, in accordance with the terms contained in a written agreement between you and Audiokinetic Inc. Copyright (c) 2023 Audiokinetic Inc. *******************************************************************************/ #include "AkSettings.h" #include "AkAcousticTexture.h" #include "AkAuxBus.h" #include "AkAudioDevice.h" #include "AkAudioEvent.h" #include "AkAudioModule.h" #include "AkSettingsPerUser.h" #include "AkUnrealHelper.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/AssetData.h" #include "Framework/Docking/TabManager.h" #include "Framework/Notifications/NotificationManager.h" #include "StringMatchAlgos/Array2D.h" #include "StringMatchAlgos/StringMatching.h" #include "UObject/UnrealType.h" #include "Widgets/Notifications/SNotificationList.h" #if WITH_EDITOR #include "AkAudioStyle.h" #include "AssetTools/Public/AssetToolsModule.h" #if UE_5_0_OR_LATER #include "HAL/PlatformFileManager.h" #else #include "HAL/PlatformFilemanager.h" #endif #include "Misc/ConfigCacheIni.h" #include "Misc/FileHelper.h" #include "Misc/MessageDialog.h" #include "Platforms/AkUEPlatform.h" #include "Settings/ProjectPackagingSettings.h" #include "SettingsEditor/Public/ISettingsEditorModule.h" #include "ISourceControlModule.h" #include "SourceControlHelpers.h" #include "AkUnrealEditorHelper.h" #if AK_SUPPORT_WAAPI #include "AkWaapiClient.h" #include "AkWaapiUtils.h" #include "Async/Async.h" bool WAAPIGetTextureParams(FGuid textureID, FAkAcousticTextureParams& params) { auto waapiClient = FAkWaapiClient::Get(); if (waapiClient != nullptr) { /* Construct the relevant WAAPI json fields. */ TArray> fromID; fromID.Add(MakeShareable(new FJsonValueString(textureID.ToString(EGuidFormats::DigitsWithHyphensInBraces)))); TSharedRef getArgsJson = FAkWaapiClient::CreateWAAPIGetArgumentJson(FAkWaapiClient::WAAPIGetFromOption::ID, fromID); TSharedRef options = MakeShareable(new FJsonObject()); TArray> StructJsonArray; StructJsonArray.Add(MakeShareable(new FJsonValueString("id"))); TArray absorptionStrings{ "@AbsorptionLow", "@AbsorptionMidLow", "@AbsorptionMidHigh", "@AbsorptionHigh" }; for (int i = 0; i < absorptionStrings.Num(); ++i) StructJsonArray.Add(MakeShareable(new FJsonValueString(absorptionStrings[i]))); options->SetArrayField(FAkWaapiClient::WAAPIStrings::RETURN, StructJsonArray); TSharedPtr outJsonResult; if (waapiClient->Call(ak::wwise::core::object::get, getArgsJson, options, outJsonResult, 500, false)) { /* Get absorption values from WAAPI return json. */ TArray> returnJson = outJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); if (returnJson.Num() > 0) { auto jsonObj = returnJson[0]->AsObject(); if (jsonObj != nullptr) { TSharedPtr absorptionObject = nullptr; for (int i = 0; i < absorptionStrings.Num(); ++i) { params.AbsorptionValues[i] = (float)(jsonObj->GetNumberField(absorptionStrings[i])) / 100.0f; } return true; } } } } return false; } bool WAAPIGetObjectColorIndex(FGuid textureID, int& index) { auto waapiClient = FAkWaapiClient::Get(); if (waapiClient != nullptr) { /* Construct the relevant WAAPI json fields. */ TArray> fromID; fromID.Add(MakeShareable(new FJsonValueString(textureID.ToString(EGuidFormats::DigitsWithHyphensInBraces)))); TSharedRef getArgsJson = FAkWaapiClient::CreateWAAPIGetArgumentJson(FAkWaapiClient::WAAPIGetFromOption::ID, fromID); TSharedRef options = MakeShareable(new FJsonObject()); TArray> StructJsonArray; StructJsonArray.Add(MakeShareable(new FJsonValueString("id"))); StructJsonArray.Add(MakeShareable(new FJsonValueString("@Color"))); options->SetArrayField(FAkWaapiClient::WAAPIStrings::RETURN, StructJsonArray); TSharedPtr outJsonResult; if (waapiClient->Call(ak::wwise::core::object::get, getArgsJson, options, outJsonResult, 500, false)) { /* Get absorption values from WAAPI return json. */ TArray> returnJson = outJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); if (returnJson.Num() > 0) { auto jsonObj = returnJson[0]->AsObject(); if (jsonObj != nullptr) { index = (int)(jsonObj->GetNumberField("@Color")); return true; } } } } return false; } bool WAAPIGetObjectOverrideColor(FGuid textureID) { auto waapiClient = FAkWaapiClient::Get(); if (waapiClient != nullptr) { /* Construct the relevant WAAPI json fields. */ TArray> fromID; fromID.Add(MakeShareable(new FJsonValueString(textureID.ToString(EGuidFormats::DigitsWithHyphensInBraces)))); TSharedRef getArgsJson = FAkWaapiClient::CreateWAAPIGetArgumentJson(FAkWaapiClient::WAAPIGetFromOption::ID, fromID); TSharedRef options = MakeShareable(new FJsonObject()); TArray> StructJsonArray; StructJsonArray.Add(MakeShareable(new FJsonValueString("id"))); StructJsonArray.Add(MakeShareable(new FJsonValueString("@OverrideColor"))); options->SetArrayField(FAkWaapiClient::WAAPIStrings::RETURN, StructJsonArray); TSharedPtr outJsonResult; if (waapiClient->Call(ak::wwise::core::object::get, getArgsJson, options, outJsonResult, 500, false)) { /* Get absorption values from WAAPI return json. */ TArray> returnJson = outJsonResult->GetArrayField(FAkWaapiClient::WAAPIStrings::RETURN); if (returnJson.Num() > 0) { auto jsonObj = returnJson[0]->AsObject(); if (jsonObj != nullptr) { return jsonObj->GetBoolField("@OverrideColor"); } } } } return false; } #endif // AK_SUPPORT_WAAPI #endif // WITH_EDITOR #define LOCTEXT_NAMESPACE "AkSettings" ////////////////////////////////////////////////////////////////////////// // UAkSettings namespace AkSettings_Helper { #if WITH_EDITOR void MigrateMultiCoreRendering(bool EnableMultiCoreRendering, const FString& PlatformName) { FString SettingsClassName = FString::Format(TEXT("Ak{0}InitializationSettings"), { *PlatformName }); #if UE_5_1_OR_LATER auto* SettingsClass = UClass::TryFindTypeSlow(*SettingsClassName); #else auto* SettingsClass = FindObject(ANY_PACKAGE, *SettingsClassName); #endif if (!SettingsClass) { return; } auto* MigrationFunction = SettingsClass->FindFunctionByName(TEXT("MigrateMultiCoreRendering")); auto* Settings = SettingsClass->GetDefaultObject(); if (!MigrationFunction || !Settings) { return; } Settings->ProcessEvent(MigrationFunction, &EnableMultiCoreRendering); AkUnrealEditorHelper::SaveConfigFile(Settings); } #endif void MatchAcousticTextureNamesToPhysMaterialNames( const TArray& PhysicalMaterials, const TArray& AcousticTextures, TArray& assignments) { uint32 NumPhysMat = (uint32)PhysicalMaterials.Num(); uint32 NumAcousticTex = (uint32)AcousticTextures.Num(); // Create a scores matrix Array2D scores(NumPhysMat, NumAcousticTex, 0); for (uint32 i = 0; i < NumPhysMat; ++i) { TArray perfectObjectMatches; perfectObjectMatches.Init(false, NumAcousticTex); if (PhysicalMaterials[i].GetAsset()) { FString physMaterialName = PhysicalMaterials[i].GetAsset()->GetName(); if (physMaterialName.Len() == 0) continue; for (uint32 j = 0; j < NumAcousticTex; ++j) { // Skip objects for which we already found a perfect match if (perfectObjectMatches[j] == true) continue; if (AcousticTextures[j].GetAsset()) { FString acousticTextureName = AcousticTextures[j].GetAsset()->GetName(); if (acousticTextureName.Len() == 0) continue; // Calculate longest common substring length float lcs = LCS::GetLCSScore( physMaterialName.ToLower(), acousticTextureName.ToLower()); scores(i, j) = lcs; if (FMath::IsNearlyEqual(lcs, 1.f)) { assignments[i] = j; perfectObjectMatches[j] = true; break; } } } } } for (uint32 i = 0; i < NumPhysMat; ++i) { if (assignments[i] == -1) { float bestScore = 0.f; int32 matchedIdx = -1; for (uint32 j = 0; j < NumAcousticTex; ++j) { if (scores(i, j) > bestScore) { bestScore = scores(i, j); matchedIdx = j; } } if (bestScore >= 0.2f) assignments[i] = matchedIdx; } } } } FString UAkSettings::DefaultSoundDataFolder = TEXT("WwiseAudio"); #if WITH_EDITOR float UAkSettings::MinimumDecayKeyDistance = 0.01f; #endif UAkSettings::UAkSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { WwiseSoundDataFolder.Path = DefaultSoundDataFolder; GlobalDecayAbsorption = 0.5f; #if WITH_EDITOR AssetRegistryModule = &FModuleManager::LoadModuleChecked("AssetRegistry"); //register to asset modification delegates auto& AssetRegistry = AssetRegistryModule->Get(); AssetRegistry.OnAssetAdded().AddUObject(this, &UAkSettings::OnAssetAdded); AssetRegistry.OnAssetRemoved().AddUObject(this, &UAkSettings::OnAssetRemoved); VisualizeRoomsAndPortals = false; bShowReverbInfo = true; #endif // WITH_EDITOR } UAkSettings::~UAkSettings() { #if WITH_EDITOR #if AK_SUPPORT_WAAPI FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); if (waapiClient != nullptr) { if (WaapiProjectLoadedHandle.IsValid()) { waapiClient->OnProjectLoaded.Remove(WaapiProjectLoadedHandle); WaapiProjectLoadedHandle.Reset(); } if (WaapiConnectionLostHandle.IsValid()) { waapiClient->OnConnectionLost.Remove(WaapiConnectionLostHandle); WaapiConnectionLostHandle.Reset(); } ClearWaapiTextureCallbacks(); } #endif #endif } ECollisionChannel UAkSettings::ConvertFitToGeomCollisionChannel(EAkCollisionChannel CollisionChannel) { if (CollisionChannel != EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault) return (ECollisionChannel)CollisionChannel; const UAkSettings* AkSettings = GetDefault(); if (AkSettings) return AkSettings->DefaultFitToGeometryCollisionChannel; return ECollisionChannel::ECC_WorldStatic; } ECollisionChannel UAkSettings::ConvertOcclusionCollisionChannel(EAkCollisionChannel CollisionChannel) { if (CollisionChannel != EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault) return (ECollisionChannel)CollisionChannel; const UAkSettings* AkSettings = GetDefault(); if (AkSettings) return AkSettings->DefaultOcclusionCollisionChannel; return ECollisionChannel::ECC_WorldStatic; } void UAkSettings::PostInitProperties() { Super::PostInitProperties(); #if WITH_EDITOR UAkSettingsPerUser* AkSettingsPerUser = GetMutableDefault(); if (AkSettingsPerUser) { bool didChanges = false; if (!WwiseWindowsInstallationPath_DEPRECATED.Path.IsEmpty()) { AkSettingsPerUser->WwiseWindowsInstallationPath = WwiseWindowsInstallationPath_DEPRECATED; WwiseWindowsInstallationPath_DEPRECATED.Path.Reset(); didChanges = true; } if (!WwiseMacInstallationPath_DEPRECATED.FilePath.IsEmpty()) { AkSettingsPerUser->WwiseMacInstallationPath = WwiseMacInstallationPath_DEPRECATED; WwiseMacInstallationPath_DEPRECATED.FilePath.Reset(); didChanges = true; } if (bAutoConnectToWAAPI_DEPRECATED) { AkSettingsPerUser->bAutoConnectToWAAPI = true; bAutoConnectToWAAPI_DEPRECATED = false; didChanges = true; } if (didChanges) { AkUnrealEditorHelper::SaveConfigFile(this); AkSettingsPerUser->SaveConfig(); } } if (!MigratedEnableMultiCoreRendering) { MigratedEnableMultiCoreRendering = true; for (const auto& PlatformName : AkUnrealPlatformHelper::GetAllSupportedWwisePlatforms()) { AkSettings_Helper::MigrateMultiCoreRendering(bEnableMultiCoreRendering_DEPRECATED, *PlatformName); } } PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; #endif // WITH_EDITOR } #if WITH_EDITOR void UAkSettings::PreEditChange(FProperty* PropertyAboutToChange) { PreviousWwiseProjectPath = WwiseProjectPath.FilePath; PreviousWwiseGeneratedSoundBankFolder = GeneratedSoundBanksFolder.Path; } bool UAkSettings::UpdateGeneratedSoundBanksPath(FString Path) { PreviousWwiseGeneratedSoundBankFolder = GeneratedSoundBanksFolder.Path; GeneratedSoundBanksFolder.Path = Path; return UpdateGeneratedSoundBanksPath(); } bool UAkSettings::GeneratedSoundBanksPathExists() const { return FPaths::DirectoryExists(AkUnrealHelper::GetSoundBankDirectory()); } bool UAkSettings::AreSoundBanksGenerated() const { return FPaths::FileExists(FPaths::Combine(AkUnrealHelper::GetSoundBankDirectory(), TEXT("ProjectInfo.json"))); } void UAkSettings::RefreshAcousticTextureParams() const { for (auto const& texture : AcousticTextureParamsMap) { OnTextureParamsChanged.Broadcast(texture.Key); } } bool UAkSettings::UpdateGeneratedSoundBanksPath() { bool bPathChanged = AkUnrealEditorHelper::SanitizeFolderPathAndMakeRelativeToContentDir( GeneratedSoundBanksFolder.Path, PreviousWwiseGeneratedSoundBankFolder, FText::FromString("Please enter a valid directory path")); if (bPathChanged) { OnGeneratedSoundBanksPathChanged.Broadcast(); } else { UE_LOG(LogAkAudio, Log, TEXT("AkSettings: The given GeneratedSoundBanks folder was the same as the previous one.")); } return bPathChanged; } void UAkSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { const FName PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None; const FName MemberPropertyName = (PropertyChangedEvent.MemberProperty != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; ISettingsEditorModule& SettingsEditorModule = FModuleManager::GetModuleChecked("SettingsEditor"); if ( PropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, MaxSimultaneousReverbVolumes)) { MaxSimultaneousReverbVolumes = FMath::Clamp( MaxSimultaneousReverbVolumes, 0, AK_MAX_AUX_PER_OBJ ); FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); if( AkAudioDevice ) { AkAudioDevice->SetMaxAuxBus(MaxSimultaneousReverbVolumes); } } else if (PropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, AudioRouting)) { OnAudioRoutingUpdate(); } else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, WwiseProjectPath)) { SanitizeProjectPath(WwiseProjectPath.FilePath, PreviousWwiseProjectPath, FText::FromString("Please enter a valid Wwise project")); } else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, AkGeometryMap)) { if (PropertyName == GET_MEMBER_NAME_CHECKED(FAkGeometrySurfacePropertiesToMap, AcousticTexture)) { for (auto& elem : AkGeometryMap) { PhysicalMaterialAcousticTextureMap[elem.Key.LoadSynchronous()] = elem.Value.AcousticTexture.LoadSynchronous(); } } else if (PropertyName == GET_MEMBER_NAME_CHECKED(FAkGeometrySurfacePropertiesToMap, OcclusionValue)) { for (auto& elem : AkGeometryMap) { PhysicalMaterialOcclusionMap[elem.Key.LoadSynchronous()] = elem.Value.OcclusionValue; } } } else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, VisualizeRoomsAndPortals)) { OnShowRoomsPortalsChanged.Broadcast(); } else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, bShowReverbInfo)) { OnShowReverbInfoChanged.Broadcast(); } else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, EnvironmentDecayAuxBusMap)) { if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { DecayAuxBusMapChanged(); OnAuxBusAssignmentMapChanged.Broadcast(); } } else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, GlobalDecayAbsorption)) { OnAuxBusAssignmentMapChanged.Broadcast(); } else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, DefaultReverbAuxBus)) { OnAuxBusAssignmentMapChanged.Broadcast(); } else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, HFDampingName) || MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, DecayEstimateName) || MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, TimeToFirstReflectionName) || MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, HFDampingRTPC) || MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, DecayEstimateRTPC) || MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, TimeToFirstReflectionRTPC)) { OnReverbRTPCChanged.Broadcast(); } else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, WwiseStagingDirectory)) { FAkAudioModule::AkAudioModuleInstance->UpdateWwiseResourceLoaderSettings(); } else if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UAkSettings, GeneratedSoundBanksFolder)) { UpdateGeneratedSoundBanksPath(); } Super::PostEditChangeProperty(PropertyChangedEvent); } void UAkSettings::ToggleVisualizeRoomsAndPortals() { VisualizeRoomsAndPortals = !VisualizeRoomsAndPortals; OnShowRoomsPortalsChanged.Broadcast(); } void UAkSettings::ToggleShowReverbInfo() { bShowReverbInfo = !bShowReverbInfo; OnShowReverbInfoChanged.Broadcast(); } void UAkSettings::FillAkGeometryMap( const TArray& PhysicalMaterialAssets, const TArray& AcousticTextureAssets) { TArray assignments; assignments.Init(-1, PhysicalMaterialAssets.Num()); AkSettings_Helper::MatchAcousticTextureNamesToPhysMaterialNames(PhysicalMaterialAssets, AcousticTextureAssets, assignments); for (int i = 0; i < PhysicalMaterialAssets.Num(); i++) { auto physicalMaterial = Cast(PhysicalMaterialAssets[i].GetAsset()); if (!PhysicalMaterialAcousticTextureMap.Contains(physicalMaterial)) { if (assignments[i] != -1) { int32 acousticTextureIdx = assignments[i]; auto acousticTexture = Cast(AcousticTextureAssets[acousticTextureIdx].GetAsset()); PhysicalMaterialAcousticTextureMap.Add(physicalMaterial, acousticTexture); } else { PhysicalMaterialAcousticTextureMap.Add(physicalMaterial); } } else { if (assignments[i] != -1) { if (!PhysicalMaterialAcousticTextureMap[physicalMaterial]) { int32 acousticTextureIdx = assignments[i]; auto acousticTexture = Cast(AcousticTextureAssets[acousticTextureIdx].GetAsset()); PhysicalMaterialAcousticTextureMap[physicalMaterial] = acousticTexture; } } } if (!PhysicalMaterialOcclusionMap.Contains(physicalMaterial)) PhysicalMaterialOcclusionMap.Add(physicalMaterial, 1.f); } UpdateAkGeometryMap(); } void UAkSettings::UpdateAkGeometryMap() { decltype(AkGeometryMap) UpdatedGeometryMap; for (const auto& AcousticTextureTuple : PhysicalMaterialAcousticTextureMap) { const auto* PhysicalMaterial(AcousticTextureTuple.Key); const auto* AcousticTexture(AcousticTextureTuple.Value); const auto* OcclusionPtr = PhysicalMaterialOcclusionMap.Find(PhysicalMaterial); if (UNLIKELY(!OcclusionPtr)) { UE_LOG(LogAkAudio, Warning, TEXT("UpdateAkGeometryMap: Could not find Occlusion of Physical Material %s with Acoustic Texture %s"), PhysicalMaterial ? *PhysicalMaterial->GetFName().ToString() : TEXT("[nullptr]"), AcousticTexture ? *AcousticTexture->GetFName().ToString() : TEXT("[nullptr]")); continue; } FAkGeometrySurfacePropertiesToMap SurfaceProperties; SurfaceProperties.AcousticTexture = AcousticTexture; SurfaceProperties.OcclusionValue = *OcclusionPtr; UpdatedGeometryMap.Emplace(PhysicalMaterial, MoveTemp(SurfaceProperties)); } UpdatedGeometryMap.KeySort([](const auto& Lhs, const auto& Rhs) { if (UNLIKELY(!Lhs.IsValid() || !Rhs.IsValid())) { return !Lhs.IsValid(); } return Lhs->GetPathName().Compare(Rhs->GetPathName()) < 0; }); if (!UpdatedGeometryMap.OrderIndependentCompareEqual(AkGeometryMap)) { UE_LOG(LogAkAudio, Verbose, TEXT("UpdateAkGeometryMap: Updating changed AkGeometryMap")); AkGeometryMap = UpdatedGeometryMap; AkUnrealEditorHelper::SaveConfigFile(this); } else { UE_LOG(LogAkAudio, VeryVerbose, TEXT("UpdateAkGeometryMap: AkGeometryMap is unchanged. Skip updating.")); } } void UAkSettings::InitAkGeometryMap() { PhysicalMaterialAcousticTextureMap.Empty(); PhysicalMaterialOcclusionMap.Empty(); // copy everything from the ini file for (auto& elem : AkGeometryMap) { auto physMat = elem.Key.LoadSynchronous(); auto surfaceProps = elem.Value; PhysicalMaterialAcousticTextureMap.Add(physMat, surfaceProps.AcousticTexture.LoadSynchronous()); PhysicalMaterialOcclusionMap.Add(physMat, surfaceProps.OcclusionValue); } bAkGeometryMapInitialized = true; // Obtain the 2 list of children we want to match TArray PhysicalMaterials, AcousticTextures; #if UE_5_1_OR_LATER AssetRegistryModule->Get().GetAssetsByClass(UPhysicalMaterial::StaticClass()->GetClassPathName(), PhysicalMaterials); AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetClassPathName(), AcousticTextures); #else AssetRegistryModule->Get().GetAssetsByClass(UPhysicalMaterial::StaticClass()->GetFName(), PhysicalMaterials); AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetFName(), AcousticTextures); #endif FillAkGeometryMap(PhysicalMaterials, AcousticTextures); } void UAkSettings::ClearAkRoomDecayAuxBusMap() { EnvironmentDecayAuxBusMap.Empty(); PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; } void UAkSettings::InsertDecayKeyValue(const float& decayKey) { if (decayKey < 0.0f) { UE_LOG(LogAkAudio, Warning, TEXT("AkSettings: Reverb Assignment Map: Decay key values must be positive.")); return; } // Refuse key value if it is too close to an existing key value (within MinimumDecayKeyDistance). TArray decayKeys; EnvironmentDecayAuxBusMap.GetKeys(decayKeys); for (int i = 0; i < decayKeys.Num(); ++i) { if (FMath::Abs(decayKeys[i] - decayKey) < MinimumDecayKeyDistance) { UE_LOG(LogAkAudio, Warning, TEXT("AkSettings: Reverb Assignment Map: New decay key too close to existing key. Must be +- %f from any existing key."), MinimumDecayKeyDistance); return; } // Keys are reverse sorted. If the new key value is larger enough than the current index, we have found a valid insert space. // (There will be no other keys larger than the current index to check against). if (decayKey - decayKeys[i] > MinimumDecayKeyDistance) break; } EnvironmentDecayAuxBusMap.Add(decayKey, nullptr); SortDecayKeys(); } void UAkSettings::SetAcousticTextureParams(const FGuid& textureID, const FAkAcousticTextureParams& params) { if (AcousticTextureParamsMap.Contains(textureID)) AcousticTextureParamsMap[textureID] = params; else AcousticTextureParamsMap.Add(textureID, params); #if AK_SUPPORT_WAAPI RegisterWaapiTextureCallback(textureID); #endif } void UAkSettings::ClearTextureParamsMap() { AcousticTextureParamsMap.Empty(); #if AK_SUPPORT_WAAPI ClearWaapiTextureCallbacks(); #endif } #if AK_SUPPORT_WAAPI void UAkSettings::WaapiProjectLoaded() { TArray keys; AcousticTextureParamsMap.GetKeys(keys); for (auto key : keys) { UpdateTextureParams(key); UpdateTextureColor(key); RegisterWaapiTextureCallback(key); } } void UAkSettings::WaapiDisconnected() { ClearWaapiTextureCallbacks(); } void UAkSettings::RegisterWaapiTextureCallback(const FGuid& textureID) { FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); if (waapiClient != nullptr && waapiClient->IsConnected()) { auto absorptionCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr jsonObject) { const TSharedPtr itemObj = jsonObject->GetObjectField(WwiseWaapiHelper::OBJECT); if (itemObj != nullptr) { const FString itemIdString = itemObj->GetStringField(WwiseWaapiHelper::ID); FGuid itemID = FGuid::NewGuid(); FGuid::ParseExact(itemIdString, EGuidFormats::DigitsWithHyphensInBraces, itemID); if (AcousticTextureParamsMap.Find(itemID) != nullptr) { AsyncTask(ENamedThreads::GameThread, [this, itemID] { UpdateTextureParams(itemID); }); } } }); TSharedRef options = MakeShareable(new FJsonObject()); options->SetStringField(WwiseWaapiHelper::OBJECT, textureID.ToString(EGuidFormats::DigitsWithHyphensInBraces)); TArray absorptionStrings{ "AbsorptionLow", "AbsorptionMidLow", "AbsorptionMidHigh", "AbsorptionHigh" }; TSharedPtr jsonResult; TSharedPtr unsubscribeResult; bool unsubscribeNeeded = WaapiTextureSubscriptions.Find(textureID) != nullptr; TArray subscriptionIDs{ 0,0,0,0 }; for (int i = 0; i < absorptionStrings.Num(); ++i) { options->SetStringField(WwiseWaapiHelper::PROPERTY, absorptionStrings[i]); if (unsubscribeNeeded) { waapiClient->Unsubscribe(WaapiTextureSubscriptions[textureID][i], unsubscribeResult); } if (!waapiClient->Subscribe(ak::wwise::core::object::propertyChanged, options, absorptionCallback, subscriptionIDs[i], jsonResult)) { UE_LOG(LogAkAudio, Warning, TEXT("AkSettings: WAAPI: Acoustic texture propertyChanged subscription failed.")); } } WaapiTextureSubscriptions.Add(textureID, subscriptionIDs); auto colorCallback = WampEventCallback::CreateLambda([this](uint64_t id, TSharedPtr jsonObject) { const TSharedPtr itemObj = jsonObject->GetObjectField(WwiseWaapiHelper::OBJECT); if (itemObj != nullptr) { const FString itemIdString = itemObj->GetStringField(WwiseWaapiHelper::ID); FGuid itemID = FGuid::NewGuid(); FGuid::ParseExact(itemIdString, EGuidFormats::DigitsWithHyphensInBraces, itemID); if (AcousticTextureParamsMap.Find(itemID) != nullptr) { AsyncTask(ENamedThreads::GameThread, [this, itemID] { UpdateTextureColor(itemID); }); } } }); options = MakeShareable(new FJsonObject()); options->SetStringField(WwiseWaapiHelper::OBJECT, textureID.ToString(EGuidFormats::DigitsWithHyphensInBraces)); unsubscribeNeeded = WaapiTextureColorSubscriptions.Find(textureID) != nullptr; uint64 subscriptionID = 0; options->SetStringField(WwiseWaapiHelper::PROPERTY, "Color"); if (unsubscribeNeeded) { waapiClient->Unsubscribe(WaapiTextureColorSubscriptions[textureID], unsubscribeResult); } if (!waapiClient->Subscribe(ak::wwise::core::object::propertyChanged, options, colorCallback, subscriptionID, jsonResult)) { UE_LOG(LogAkAudio, Warning, TEXT("AkSettings: WAAPI: Acoustic texture Color propertyChanged subscription failed.")); } WaapiTextureColorSubscriptions.Add(textureID, subscriptionID); unsubscribeNeeded = WaapiTextureColorOverrideSubscriptions.Find(textureID) != nullptr; subscriptionID = 0; options->SetStringField(WwiseWaapiHelper::PROPERTY, "OverrideColor"); if (unsubscribeNeeded) { waapiClient->Unsubscribe(WaapiTextureColorOverrideSubscriptions[textureID], unsubscribeResult); } if (!waapiClient->Subscribe(ak::wwise::core::object::propertyChanged, options, colorCallback, subscriptionID, jsonResult)) { UE_LOG(LogAkAudio, Warning, TEXT("AkSettings: WAAPI: Acoustic texture OverrideColor propertyChanged subscription failed.")); } WaapiTextureColorOverrideSubscriptions.Add(textureID, subscriptionID); } } void UAkSettings::UnregisterWaapiTextureCallback(const FGuid& textureID) { FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); if (waapiClient != nullptr && waapiClient->IsConnected()) { if (WaapiTextureSubscriptions.Find(textureID) != nullptr) { TSharedPtr unsubscribeResult; for (int i = 0; i < WaapiTextureSubscriptions[textureID].Num(); ++i) waapiClient->Unsubscribe(WaapiTextureSubscriptions[textureID][i], unsubscribeResult); WaapiTextureSubscriptions.Remove(textureID); } if (WaapiTextureColorSubscriptions.Find(textureID) != nullptr) { TSharedPtr unsubscribeResult; waapiClient->Unsubscribe(WaapiTextureColorSubscriptions[textureID], unsubscribeResult); WaapiTextureColorSubscriptions.Remove(textureID); } if (WaapiTextureColorOverrideSubscriptions.Find(textureID) != nullptr) { TSharedPtr unsubscribeResult; waapiClient->Unsubscribe(WaapiTextureColorOverrideSubscriptions[textureID], unsubscribeResult); WaapiTextureColorOverrideSubscriptions.Remove(textureID); } } } void UAkSettings::ClearWaapiTextureCallbacks() { FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); if (waapiClient != nullptr && waapiClient->IsConnected()) { for (auto it = WaapiTextureSubscriptions.CreateIterator(); it; ++it) { TSharedPtr unsubscribeResult; for (int i = 0; i < it.Value().Num(); ++i) waapiClient->Unsubscribe(it.Value()[i], unsubscribeResult); } for (auto it = WaapiTextureColorSubscriptions.CreateIterator(); it; ++it) { TSharedPtr unsubscribeResult; waapiClient->Unsubscribe(it.Value(), unsubscribeResult); } for (auto it = WaapiTextureColorOverrideSubscriptions.CreateIterator(); it; ++it) { TSharedPtr unsubscribeResult; waapiClient->Unsubscribe(it.Value(), unsubscribeResult); } WaapiTextureSubscriptions.Empty(); WaapiTextureColorSubscriptions.Empty(); WaapiTextureColorOverrideSubscriptions.Empty(); } } void UAkSettings::UpdateTextureParams(const FGuid& textureID) { WAAPIGetTextureParams(textureID, AcousticTextureParamsMap[textureID]); OnTextureParamsChanged.Broadcast(textureID); } void UAkSettings::UpdateTextureColor(const FGuid& textureID) { if (!WAAPIGetObjectOverrideColor(textureID)) { SetTextureColor(textureID, -1); return; } int colorIndex = 0; if (WAAPIGetObjectColorIndex(textureID, colorIndex)) { SetTextureColor(textureID, colorIndex); } } void UAkSettings::SetTextureColor(FGuid textureID, int colorIndex) { TArray AcousticTextures; #if UE_5_1_OR_LATER AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetClassPathName(), AcousticTextures); #else AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetFName(), AcousticTextures); #endif FLinearColor color = FAkAudioStyle::GetWwiseObjectColor(colorIndex); for (FAssetData& textureAsset : AcousticTextures) { if (UAkAcousticTexture* texture = Cast(textureAsset.GetAsset())) { if (texture->AcousticTextureInfo.WwiseGuid == textureID && texture->EditColor != color) { texture->Modify(); texture->EditColor = color; break; } } } } #endif // AK_SUPPORT_WAAPI void UAkSettings::DecayAuxBusMapChanged() { // If a key has been moved beyond its neighbours, restrict it between neighbouring key values // Removal - nothing to restrict if (PreviousDecayAuxBusMap.Num() > EnvironmentDecayAuxBusMap.Num()) { PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; return; } // Addition - when the insert button is used, restrictions will already be handled. // If the stock Unreal '+' button is used, a 0-nullptr entry will be added at the end of the map. In this case, // remove it and insert it using the InsertDecayKeyValue so it can be properly restricted and placed. if (PreviousDecayAuxBusMap.Num() < EnvironmentDecayAuxBusMap.Num()) { if (EnvironmentDecayAuxBusMap.Num() > 1) { TArray keys; EnvironmentDecayAuxBusMap.GetKeys(keys); if (keys.Last() == 0.0f && EnvironmentDecayAuxBusMap[0.0f] == nullptr) { EnvironmentDecayAuxBusMap.Remove(0.0f); InsertDecayKeyValue(0.0f); } } PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; return; } // Find key that has changed int changedKeyIndex = -1; const int numKeys = PreviousDecayAuxBusMap.Num(); TArray previousKeys; TArray newKeys; PreviousDecayAuxBusMap.GetKeys(previousKeys); EnvironmentDecayAuxBusMap.GetKeys(newKeys); for (int i = 0; i < numKeys; ++i) { if (!FMath::IsNearlyEqual(previousKeys[i], newKeys[i], 1.0e-06F)) // Floating point property values have a tendancy to gradually wander in UE. { changedKeyIndex = i; break; } } // If no key values have changed, an aux bus has been changed. Nothing to restrict. if (changedKeyIndex == -1) return; // check key value float newKeyValue = newKeys[changedKeyIndex]; float restrictedKeyValue = newKeyValue; FString restrictionInfoString; // Keys are sorted in reverse order such that they are ordered vertically from smallest to largest, in the UI. // So, check newKeyValue <= newKeys[changedKeyIndex + 1] and newKeyValue >= newKeys[changedKeyIndex - 1]. const bool changedKeyIsSmallest = changedKeyIndex == numKeys - 1; float lowerLimit = changedKeyIsSmallest ? 0.0f : newKeys[changedKeyIndex + 1]; if (newKeyValue <= lowerLimit) { restrictedKeyValue = changedKeyIsSmallest ? 0.0f : lowerLimit + MinimumDecayKeyDistance; restrictionInfoString = FString("Decay key value limited by next lowest value."); } else if (changedKeyIndex > 0 && newKeyValue >= newKeys[changedKeyIndex - 1]) { restrictedKeyValue = newKeys[changedKeyIndex - 1] - MinimumDecayKeyDistance; restrictionInfoString = FString("Decay key value limited by next highest value."); } // If key value needs to be restricted, remove and replace the entry in the map. if (restrictedKeyValue != newKeyValue) { FAkAudioStyle::DisplayEditorMessage(FText::FromString(restrictionInfoString)); TSoftObjectPtr auxBusToMove = EnvironmentDecayAuxBusMap[newKeyValue]; EnvironmentDecayAuxBusMap.Remove(newKeyValue); EnvironmentDecayAuxBusMap.Add(restrictedKeyValue, auxBusToMove); SortDecayKeys(); } else // No restriction to apply, but still keep track of the new key values. { PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; } } void UAkSettings::SortDecayKeys() { // high to low decay ('large' to 'small' environment structure) EnvironmentDecayAuxBusMap.KeySort([](const float& left, const float& right) {return left > right; }); PreviousDecayAuxBusMap = EnvironmentDecayAuxBusMap; } void UAkSettings::OnAssetAdded(const FAssetData& NewAssetData) { if (!bAkGeometryMapInitialized) return; #if UE_5_1_OR_LATER if (NewAssetData.AssetClassPath == UPhysicalMaterial::StaticClass()->GetClassPathName()) #else if (NewAssetData.AssetClass == UPhysicalMaterial::StaticClass()->GetFName()) #endif { if (auto physicalMaterial = Cast(NewAssetData.GetAsset())) { TArray PhysicalMaterials, AcousticTextures; PhysicalMaterials.Add(NewAssetData); #if UE_5_1_OR_LATER AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetClassPathName(), AcousticTextures); #else AssetRegistryModule->Get().GetAssetsByClass(UAkAcousticTexture::StaticClass()->GetFName(), AcousticTextures); #endif FillAkGeometryMap(PhysicalMaterials, AcousticTextures); } } #if UE_5_1_OR_LATER else if (NewAssetData.AssetClassPath == UAkAcousticTexture::StaticClass()->GetClassPathName()) #else else if (NewAssetData.AssetClass == UAkAcousticTexture::StaticClass()->GetFName()) #endif { if (auto acousticTexture = Cast(NewAssetData.GetAsset())) { TArray PhysicalMaterials, AcousticTextures; #if UE_5_1_OR_LATER AssetRegistryModule->Get().GetAssetsByClass(UPhysicalMaterial::StaticClass()->GetClassPathName(), PhysicalMaterials); #else AssetRegistryModule->Get().GetAssetsByClass(UPhysicalMaterial::StaticClass()->GetFName(), PhysicalMaterials); #endif AcousticTextures.Add(NewAssetData); FillAkGeometryMap(PhysicalMaterials, AcousticTextures); FAkAcousticTextureParams params; bool paramsExist = AcousticTextureParamsMap.Contains(acousticTexture->AcousticTextureInfo.WwiseGuid); if (paramsExist) { params = *AcousticTextureParamsMap.Find(acousticTexture->AcousticTextureInfo.WwiseGuid); params.shortID = acousticTexture->AcousticTextureInfo.WwiseShortId; } #if AK_SUPPORT_WAAPI bool paramsSet = WAAPIGetTextureParams(acousticTexture->AcousticTextureInfo.WwiseGuid, params); if (paramsSet && !paramsExist) AcousticTextureParamsMap.Add(acousticTexture->AcousticTextureInfo.WwiseGuid, params); RegisterWaapiTextureCallback(acousticTexture->AcousticTextureInfo.WwiseGuid); int colorIndex = -1; if (WAAPIGetObjectColorIndex(acousticTexture->AcousticTextureInfo.WwiseGuid, colorIndex)) { acousticTexture->EditColor = FAkAudioStyle::GetWwiseObjectColor(colorIndex); } #endif } } } void UAkSettings::OnAssetRemoved(const struct FAssetData& AssetData) { #if UE_5_1_OR_LATER if (AssetData.AssetClassPath == UPhysicalMaterial::StaticClass()->GetClassPathName()) #else if (AssetData.AssetClass == UPhysicalMaterial::StaticClass()->GetFName()) #endif { if (auto physicalMaterial = Cast(AssetData.GetAsset())) { PhysicalMaterialAcousticTextureMap.Remove(physicalMaterial); PhysicalMaterialOcclusionMap.Remove(physicalMaterial); UpdateAkGeometryMap(); } } #if UE_5_1_OR_LATER else if(AssetData.AssetClassPath == UAkAcousticTexture::StaticClass()->GetClassPathName()) #else else if(AssetData.AssetClass == UAkAcousticTexture::StaticClass()->GetFName()) #endif { if(auto acousticTexture = Cast(AssetData.GetAsset())) { AcousticTextureParamsMap.Remove(acousticTexture->AcousticTextureInfo.WwiseGuid); #if AK_SUPPORT_WAAPI UnregisterWaapiTextureCallback(acousticTexture->AcousticTextureInfo.WwiseGuid); #endif } } } #if AK_SUPPORT_WAAPI void UAkSettings::InitWaapiSync() { FAkWaapiClient* waapiClient = FAkWaapiClient::Get(); if (waapiClient != nullptr) { if (waapiClient->IsProjectLoaded()) WaapiProjectLoaded(); WaapiProjectLoadedHandle = waapiClient->OnProjectLoaded.AddLambda([this]() { WaapiProjectLoaded(); }); WaapiConnectionLostHandle = waapiClient->OnConnectionLost.AddLambda([this]() { WaapiDisconnected(); }); } } #endif void UAkSettings::EnsurePluginContentIsInAlwaysCook() const { UProjectPackagingSettings* PackagingSettings = GetMutableDefault(); bool packageSettingsNeedUpdate = false; TArray PathsToCheck = { TEXT("/Wwise/WwiseTree"), TEXT("/Wwise/WwiseTypes") }; for (auto pathToCheck : PathsToCheck) { if (!PackagingSettings->DirectoriesToAlwaysCook.ContainsByPredicate([pathToCheck](FDirectoryPath PathInArray) { return PathInArray.Path == pathToCheck; })) { FDirectoryPath newPath; newPath.Path = pathToCheck; PackagingSettings->DirectoriesToAlwaysCook.Add(newPath); packageSettingsNeedUpdate = true; } } if (packageSettingsNeedUpdate) { AkUnrealEditorHelper::SaveConfigFile(PackagingSettings); } } void UAkSettings::RemoveSoundDataFromAlwaysCook(const FString& SoundDataPath) { bool changed = false; UProjectPackagingSettings* PackagingSettings = GetMutableDefault(); for (int32 i = PackagingSettings->DirectoriesToAlwaysCook.Num() - 1; i >= 0; --i) { if (PackagingSettings->DirectoriesToAlwaysCook[i].Path == SoundDataPath) { PackagingSettings->DirectoriesToAlwaysCook.RemoveAt(i); changed = true; break; } } if (changed) { AkUnrealEditorHelper::SaveConfigFile(PackagingSettings); } } void UAkSettings::SanitizeProjectPath(FString& Path, const FString& PreviousPath, const FText& DialogMessage) { AkUnrealHelper::TrimPath(Path); FString TempPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*Path); FText FailReason; if (!FPaths::ValidatePath(TempPath, &FailReason)) { if (EAppReturnType::Ok == FMessageDialog::Open(EAppMsgType::Ok, FailReason)) { Path = PreviousPath; return; } } auto ProjectDirectory = AkUnrealHelper::GetProjectDirectory(); if (!FPaths::FileExists(TempPath)) { // Path might be a valid one (relative to game) entered manually. Check that. TempPath = FPaths::ConvertRelativePathToFull(ProjectDirectory, Path); if (!FPaths::FileExists(TempPath)) { if (EAppReturnType::Ok == FMessageDialog::Open(EAppMsgType::Ok, DialogMessage)) { Path = PreviousPath; return; } } } // Make the path relative to the game dir FPaths::MakePathRelativeTo(TempPath, *ProjectDirectory); Path = TempPath; if (Path != PreviousPath) { #if UE_4_26_OR_LATER auto WwiseBrowserTab = FGlobalTabmanager::Get()->TryInvokeTab(FName("WwiseBrowser")); #else TSharedRef WwiseBrowserTab = FGlobalTabmanager::Get()->InvokeTab(FName("WwiseBrowser")); #endif bRequestRefresh = true; } } void UAkSettings::OnAudioRoutingUpdate() { // Calculate what is expected bool bExpectedCustom = false; bool bExpectedSeparate = false; bool bExpectedUsingAudioMixer = false; bool bExpectedAudioModuleOverride = true; bool bExpectedWwiseSoundEngineEnabled = true; bool bExpectedWwiseAudioLinkEnabled = false; bool bExpectedAkAudioMixerEnabled = false; FString ExpectedAudioDeviceModuleName; FString ExpectedAudioMixerModuleName; switch (AudioRouting) { case EAkUnrealAudioRouting::Custom: UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for Custom")); bExpectedCustom = true; break; case EAkUnrealAudioRouting::Separate: UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for Separate")); bExpectedSeparate = true; bExpectedUsingAudioMixer = true; bExpectedAudioModuleOverride = false; break; case EAkUnrealAudioRouting::EnableWwiseOnly: UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for DisableUnreal")); bExpectedUsingAudioMixer = false; ExpectedAudioDeviceModuleName = TEXT(""); ExpectedAudioMixerModuleName = TEXT(""); break; case EAkUnrealAudioRouting::EnableUnrealOnly: UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for DisableWwise")); bExpectedSeparate = true; bExpectedUsingAudioMixer = true; bExpectedAudioModuleOverride = false; bExpectedWwiseSoundEngineEnabled = false; break; case EAkUnrealAudioRouting::AudioMixer: UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for AudioMixer")); bExpectedUsingAudioMixer = true; bExpectedAkAudioMixerEnabled = true; ExpectedAudioDeviceModuleName = TEXT("AkAudioMixer"); ExpectedAudioMixerModuleName = TEXT("AkAudioMixer"); break; case EAkUnrealAudioRouting::AudioLink: UE_LOG(LogAkAudio, VeryVerbose, TEXT("OnAudioRoutingUpdate: Setting for AudioLink")); bExpectedSeparate = true; bExpectedUsingAudioMixer = true; bExpectedWwiseAudioLinkEnabled = true; bExpectedAudioModuleOverride = false; break; default: UE_LOG(LogAkAudio, Warning, TEXT("OnAudioRoutingUpdate: Unknown AudioRouting")); return; } // // Actually update the files // UE_LOG(LogAkAudio, Verbose, TEXT("OnAudioRoutingUpdate: Updating system settings.")); { bWwiseSoundEngineEnabled = bExpectedWwiseSoundEngineEnabled; UE_LOG(LogAkAudio, Log, TEXT("OnAudioRoutingUpdate: Wwise SoundEngine Enabled: %s"), bExpectedWwiseSoundEngineEnabled ? TEXT("true") : TEXT("false")); bWwiseAudioLinkEnabled = bExpectedWwiseAudioLinkEnabled; UE_LOG(LogAkAudio, Log, TEXT("OnAudioRoutingUpdate: Wwise AudioLink Enabled: %s"), bExpectedWwiseAudioLinkEnabled ? TEXT("true") : TEXT("false")); bAkAudioMixerEnabled = bExpectedAkAudioMixerEnabled; UE_LOG(LogAkAudio, Log, TEXT("OnAudioRoutingUpdate: Wwise AudioMixer Enabled: %s"), bExpectedAkAudioMixerEnabled ? TEXT("true") : TEXT("false")); #if UE_5_0_OR_LATER TryUpdateDefaultConfigFile(); #else UpdateDefaultConfigFile(); #endif } TArray IniPlatformNames; #if UE_5_0_OR_LATER for (const auto& PlatformInfo : FDataDrivenPlatformInfoRegistry::GetAllPlatformInfos()) { if (!PlatformInfo.Value.bIsFakePlatform) { IniPlatformNames.Add(PlatformInfo.Value.IniPlatformName.ToString()); } } #else for (const auto& Platform : GetTargetPlatformManagerRef().GetTargetPlatforms()) { IniPlatformNames.Add(Platform->IniPlatformName()); } #endif for (const auto& IniPlatformName : IniPlatformNames) { const auto RelativePlatformEnginePath = FString::Printf(TEXT("%s/%sEngine.ini"), *IniPlatformName, *IniPlatformName); auto PlatformEnginePath = FString::Printf(TEXT("%s%s"), *FPaths::SourceConfigDir(), *RelativePlatformEnginePath); #if UE_5_1_OR_LATER PlatformEnginePath = FConfigCacheIni::NormalizeConfigIniPath(PlatformEnginePath); #else FPaths::RemoveDuplicateSlashes(PlatformEnginePath); PlatformEnginePath = FPaths::CreateStandardFilename(PlatformEnginePath); #endif const FString FullPlatformEnginePath = FPaths::ConvertRelativePathToFull(PlatformEnginePath); if (FPlatformFileManager::Get().GetPlatformFile().FileExists(*FullPlatformEnginePath)) { FText ErrorMessage; if (ISourceControlModule::Get().IsEnabled()) { if (SourceControlHelpers::CheckoutOrMarkForAdd(FullPlatformEnginePath, FText::FromString(FullPlatformEnginePath), NULL, ErrorMessage)) { ErrorMessage = FText(); } } else if (!FPlatformFileManager::Get().GetPlatformFile().SetReadOnly(*FullPlatformEnginePath, false)) { ErrorMessage = FText::Format(LOCTEXT("FailedToMakeWritable", "Could not make {0} writable."), FText::FromString(FullPlatformEnginePath)); } if (!ErrorMessage.IsEmpty()) { FNotificationInfo Info(ErrorMessage); Info.ExpireDuration = 3.0; FSlateNotificationManager::Get().AddNotification(Info); continue; } } if (bExpectedUsingAudioMixer) { UE_LOG(LogAkAudio, Log, TEXT("%s: Removing UseAudioMixer override"), *RelativePlatformEnginePath); GConfig->RemoveKey(TEXT("Audio"), TEXT("UseAudioMixer"), PlatformEnginePath); } else { UE_LOG(LogAkAudio, Log, TEXT("%s: Updating UseAudioMixer to: %s"), *RelativePlatformEnginePath, bExpectedUsingAudioMixer ? TEXT("true") : TEXT("false")); GConfig->SetBool(TEXT("Audio"), TEXT("UseAudioMixer"), bExpectedUsingAudioMixer, PlatformEnginePath); } if (bExpectedAudioModuleOverride) { UE_LOG(LogAkAudio, Log, TEXT("%s: Updating AudioDeviceModuleName: %s"), *RelativePlatformEnginePath, ExpectedAudioDeviceModuleName.IsEmpty() ? TEXT("[empty]") : *ExpectedAudioDeviceModuleName); UE_LOG(LogAkAudio, Log, TEXT("%s: Updating AudioMixerModuleName: %s"), *RelativePlatformEnginePath, ExpectedAudioMixerModuleName.IsEmpty() ? TEXT("[empty]") : *ExpectedAudioMixerModuleName); GConfig->SetString(TEXT("Audio"), TEXT("AudioDeviceModuleName"), *ExpectedAudioDeviceModuleName, PlatformEnginePath); GConfig->SetString(TEXT("Audio"), TEXT("AudioMixerModuleName"), *ExpectedAudioMixerModuleName, PlatformEnginePath); } else { UE_LOG(LogAkAudio, Log, TEXT("%s: Removing AudioDeviceModuleName override"), *RelativePlatformEnginePath); UE_LOG(LogAkAudio, Log, TEXT("%s: Removing AudioMixerModuleName override"), *RelativePlatformEnginePath); GConfig->RemoveKey(TEXT("Audio"), TEXT("AudioDeviceModuleName"), PlatformEnginePath); GConfig->RemoveKey(TEXT("Audio"), TEXT("AudioMixerModuleName"), PlatformEnginePath); } GConfig->Flush(false, PlatformEnginePath); } } void UAkSettings::RemoveSoundDataFromAlwaysStageAsUFS(const FString& SoundDataPath) { bool changed = false; UProjectPackagingSettings* PackagingSettings = GetMutableDefault(); for (int32 i = PackagingSettings->DirectoriesToAlwaysStageAsUFS.Num() - 1; i >= 0; --i) { if (PackagingSettings->DirectoriesToAlwaysStageAsUFS[i].Path == SoundDataPath) { PackagingSettings->DirectoriesToAlwaysStageAsUFS.RemoveAt(i); changed = true; break; } } if (changed) { AkUnrealEditorHelper::SaveConfigFile(PackagingSettings); } } #endif // WITH_EDITOR const FAkAcousticTextureParams* UAkSettings::GetTextureParams(const uint32& shortID) const { for (auto it = AcousticTextureParamsMap.CreateConstIterator(); it; ++it) { if (it.Value().shortID == shortID) return AcousticTextureParamsMap.Find(it.Key()); } return nullptr; } bool UAkSettings::ReverbRTPCsInUse() const { return DecayRTPCInUse() || DampingRTPCInUse() || PredelayRTPCInUse(); } bool UAkSettings::DecayRTPCInUse() const { const bool validPath = !DecayEstimateRTPC.ToSoftObjectPath().ToString().IsEmpty(); return validPath || !DecayEstimateName.IsEmpty(); } bool UAkSettings::DampingRTPCInUse() const { const bool validPath = !HFDampingRTPC.ToSoftObjectPath().ToString().IsEmpty(); return validPath || !HFDampingName.IsEmpty(); } bool UAkSettings::PredelayRTPCInUse() const { const bool validPath = !TimeToFirstReflectionRTPC.ToSoftObjectPath().ToString().IsEmpty(); return validPath || !TimeToFirstReflectionName.IsEmpty(); } bool UAkSettings::GetAssociatedAcousticTexture(const UPhysicalMaterial* physMaterial, UAkAcousticTexture*& acousticTexture) const { TSoftObjectPtr physMatPtr(physMaterial); auto props = AkGeometryMap.Find(physMatPtr); if (!props) return false; TSoftObjectPtr texturePtr = props->AcousticTexture; acousticTexture = texturePtr.LoadSynchronous(); return true; } bool UAkSettings::GetAssociatedOcclusionValue(const UPhysicalMaterial* physMaterial, float& occlusionValue) const { TSoftObjectPtr physMatPtr(physMaterial); auto props = AkGeometryMap.Find(physMatPtr); if (!props) return false; occlusionValue = props->OcclusionValue; return true; } void UAkSettings::GetAuxBusForDecayValue(float decay, UAkAuxBus*& auxBus) { TArray decayKeys; EnvironmentDecayAuxBusMap.GetKeys(decayKeys); decayKeys.Sort(); int numKeys = decayKeys.Num(); if (numKeys > 0) { int i = numKeys - 1; if (decay > decayKeys[i]) { auxBus = DefaultReverbAuxBus.LoadSynchronous(); return; } while (i > 0 && decay <= decayKeys[i - 1]) { --i; } TSoftObjectPtr auxBusPtr = EnvironmentDecayAuxBusMap[decayKeys[i]]; auxBus = auxBusPtr.LoadSynchronous(); } else { auxBus = DefaultReverbAuxBus.LoadSynchronous(); } } void UAkSettings::GetAudioInputEvent(UAkAudioEvent*& OutInputEvent) { OutInputEvent = AudioInputEvent.LoadSynchronous(); } #undef LOCTEXT_NAMESPACE