// Copyright Epic Games, Inc. All Rights Reserved. #include "Audio/LyraAudioMixEffectsSubsystem.h" #include "AudioModulationStatics.h" #include "LyraAudioSettings.h" #include "Settings/LyraSettingsLocal.h" #include "AudioMixerBlueprintLibrary.h" #include "LoadingScreenManager.h" #include "Engine/GameInstance.h" void ULyraAudioMixEffectsSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); } void ULyraAudioMixEffectsSubsystem::Deinitialize() { if (ULoadingScreenManager* LoadingScreenManager = UGameInstance::GetSubsystem(GetWorld()->GetGameInstance())) { LoadingScreenManager->OnLoadingScreenVisibilityChangedDelegate().RemoveAll(this); ApplyOrRemoveLoadingScreenMix(false); } Super::Deinitialize(); } bool ULyraAudioMixEffectsSubsystem::ShouldCreateSubsystem(UObject* Outer) const { bool bShouldCreateSubsystem = Super::ShouldCreateSubsystem(Outer); if (Outer) { if (UWorld* World = Outer->GetWorld()) { bShouldCreateSubsystem = DoesSupportWorldType(World->WorldType) && bShouldCreateSubsystem; } } return bShouldCreateSubsystem; } void ULyraAudioMixEffectsSubsystem::PostInitialize() { if (const ULyraAudioSettings* LyraAudioSettings = GetDefault()) { if (UObject* ObjPath = LyraAudioSettings->DefaultControlBusMix.TryLoad()) { if (USoundControlBusMix* SoundControlBusMix = Cast(ObjPath)) { DefaultBaseMix = SoundControlBusMix; } else { ensureMsgf(SoundControlBusMix, TEXT("Default Control Bus Mix reference missing from Lyra Audio Settings.")); } } if (UObject* ObjPath = LyraAudioSettings->LoadingScreenControlBusMix.TryLoad()) { if (USoundControlBusMix* SoundControlBusMix = Cast(ObjPath)) { LoadingScreenMix = SoundControlBusMix; } else { ensureMsgf(SoundControlBusMix, TEXT("Loading Screen Control Bus Mix reference missing from Lyra Audio Settings.")); } } if (UObject* ObjPath = LyraAudioSettings->UserSettingsControlBusMix.TryLoad()) { if (USoundControlBusMix* SoundControlBusMix = Cast(ObjPath)) { UserMix = SoundControlBusMix; } else { ensureMsgf(SoundControlBusMix, TEXT("User Control Bus Mix reference missing from Lyra Audio Settings.")); } } if (UObject* ObjPath = LyraAudioSettings->OverallVolumeControlBus.TryLoad()) { if (USoundControlBus* SoundControlBus = Cast(ObjPath)) { OverallControlBus = SoundControlBus; } else { ensureMsgf(SoundControlBus, TEXT("Overall Control Bus reference missing from Lyra Audio Settings.")); } } if (UObject* ObjPath = LyraAudioSettings->MusicVolumeControlBus.TryLoad()) { if (USoundControlBus* SoundControlBus = Cast(ObjPath)) { MusicControlBus = SoundControlBus; } else { ensureMsgf(SoundControlBus, TEXT("Music Control Bus reference missing from Lyra Audio Settings.")); } } if (UObject* ObjPath = LyraAudioSettings->SoundFXVolumeControlBus.TryLoad()) { if (USoundControlBus* SoundControlBus = Cast(ObjPath)) { SoundFXControlBus = SoundControlBus; } else { ensureMsgf(SoundControlBus, TEXT("SoundFX Control Bus reference missing from Lyra Audio Settings.")); } } if (UObject* ObjPath = LyraAudioSettings->DialogueVolumeControlBus.TryLoad()) { if (USoundControlBus* SoundControlBus = Cast(ObjPath)) { DialogueControlBus = SoundControlBus; } else { ensureMsgf(SoundControlBus, TEXT("Dialogue Control Bus reference missing from Lyra Audio Settings.")); } } if (UObject* ObjPath = LyraAudioSettings->VoiceChatVolumeControlBus.TryLoad()) { if (USoundControlBus* SoundControlBus = Cast(ObjPath)) { VoiceChatControlBus = SoundControlBus; } else { ensureMsgf(SoundControlBus, TEXT("VoiceChat Control Bus reference missing from Lyra Audio Settings.")); } } // Load HDR Submix Effect Chain for (const FLyraSubmixEffectChainMap& SoftSubmixEffectChain : LyraAudioSettings->HDRAudioSubmixEffectChain) { FLyraAudioSubmixEffectsChain NewEffectChain; if (UObject* SubmixObjPath = SoftSubmixEffectChain.Submix.LoadSynchronous()) { if (USoundSubmix* Submix = Cast(SubmixObjPath)) { NewEffectChain.Submix = Submix; TArray NewPresetChain; for (const TSoftObjectPtr& SoftEffect : SoftSubmixEffectChain.SubmixEffectChain) { if (UObject* EffectObjPath = SoftEffect.LoadSynchronous()) { if (USoundEffectSubmixPreset* SubmixPreset = Cast(EffectObjPath)) { NewPresetChain.Add(SubmixPreset); } } } NewEffectChain.SubmixEffectChain.Append(NewPresetChain); } } HDRSubmixEffectChain.Add(NewEffectChain); } // Load LDR Submix Effect Chain for (const FLyraSubmixEffectChainMap& SoftSubmixEffectChain : LyraAudioSettings->LDRAudioSubmixEffectChain) { FLyraAudioSubmixEffectsChain NewEffectChain; if (UObject* SubmixObjPath = SoftSubmixEffectChain.Submix.LoadSynchronous()) { if (USoundSubmix* Submix = Cast(SubmixObjPath)) { NewEffectChain.Submix = Submix; TArray NewPresetChain; for (const TSoftObjectPtr& SoftEffect : SoftSubmixEffectChain.SubmixEffectChain) { if (UObject* EffectObjPath = SoftEffect.LoadSynchronous()) { if (USoundEffectSubmixPreset* SubmixPreset = Cast(EffectObjPath)) { NewPresetChain.Add(SubmixPreset); } } } NewEffectChain.SubmixEffectChain.Append(NewPresetChain); } } LDRSubmixEffectChain.Add(NewEffectChain); } } // Register with the loading screen manager if (ULoadingScreenManager* LoadingScreenManager = UGameInstance::GetSubsystem(GetWorld()->GetGameInstance())) { LoadingScreenManager->OnLoadingScreenVisibilityChangedDelegate().AddUObject(this, &ThisClass::OnLoadingScreenStatusChanged); ApplyOrRemoveLoadingScreenMix(LoadingScreenManager->GetLoadingScreenDisplayStatus()); } } void ULyraAudioMixEffectsSubsystem::OnWorldBeginPlay(UWorld& InWorld) { if (const UWorld* World = InWorld.GetWorld()) { // Activate the default base mix if (DefaultBaseMix) { UAudioModulationStatics::ActivateBusMix(World, DefaultBaseMix); } // Retrieve the user settings if (const ULyraSettingsLocal* LyraSettingsLocal = GetDefault()) { // Activate the User Mix if (UserMix) { UAudioModulationStatics::ActivateBusMix(World, UserMix); if (OverallControlBus && MusicControlBus && SoundFXControlBus && DialogueControlBus && VoiceChatControlBus) { const FSoundControlBusMixStage OverallControlBusMixStage = UAudioModulationStatics::CreateBusMixStage(World, OverallControlBus, LyraSettingsLocal->GetOverallVolume()); const FSoundControlBusMixStage MusicControlBusMixStage = UAudioModulationStatics::CreateBusMixStage(World, MusicControlBus, LyraSettingsLocal->GetMusicVolume()); const FSoundControlBusMixStage SoundFXControlBusMixStage = UAudioModulationStatics::CreateBusMixStage(World, SoundFXControlBus, LyraSettingsLocal->GetSoundFXVolume()); const FSoundControlBusMixStage DialogueControlBusMixStage = UAudioModulationStatics::CreateBusMixStage(World, DialogueControlBus, LyraSettingsLocal->GetDialogueVolume()); const FSoundControlBusMixStage VoiceChatControlBusMixStage = UAudioModulationStatics::CreateBusMixStage(World, VoiceChatControlBus, LyraSettingsLocal->GetVoiceChatVolume()); TArray ControlBusMixStageArray; ControlBusMixStageArray.Add(OverallControlBusMixStage); ControlBusMixStageArray.Add(MusicControlBusMixStage); ControlBusMixStageArray.Add(SoundFXControlBusMixStage); ControlBusMixStageArray.Add(DialogueControlBusMixStage); ControlBusMixStageArray.Add(VoiceChatControlBusMixStage); UAudioModulationStatics::UpdateMix(World, UserMix, ControlBusMixStageArray); } } ApplyDynamicRangeEffectsChains(LyraSettingsLocal->IsHDRAudioModeEnabled()); } } } void ULyraAudioMixEffectsSubsystem::ApplyDynamicRangeEffectsChains(bool bHDRAudio) { TArray AudioSubmixEffectsChainToApply; TArray AudioSubmixEffectsChainToClear; // If HDR Audio is selected, then we clear out any existing LDR Submix Effect Chain Overrides // otherwise the reverse is the case. if (bHDRAudio) { AudioSubmixEffectsChainToApply.Append(HDRSubmixEffectChain); AudioSubmixEffectsChainToClear.Append(LDRSubmixEffectChain); } else { AudioSubmixEffectsChainToApply.Append(LDRSubmixEffectChain); AudioSubmixEffectsChainToClear.Append(HDRSubmixEffectChain); } // We want to collect just the submixes we need to actually clear, otherwise they'll be overridden by the new settings TArray SubmixesLeftToClear; // We want to get the submixes that are not being overridden by the new effect chains, so we can clear those out separately for (const FLyraAudioSubmixEffectsChain& EffectChainToClear : AudioSubmixEffectsChainToClear) { bool bAddToList = true; for (const FLyraAudioSubmixEffectsChain& SubmixEffectChain : AudioSubmixEffectsChainToApply) { if (SubmixEffectChain.Submix == EffectChainToClear.Submix) { bAddToList = false; break; } } if (bAddToList) { SubmixesLeftToClear.Add(EffectChainToClear.Submix); } } // Override submixes for (const FLyraAudioSubmixEffectsChain& SubmixEffectChain : AudioSubmixEffectsChainToApply) { if (SubmixEffectChain.Submix) { UAudioMixerBlueprintLibrary::SetSubmixEffectChainOverride(GetWorld(), SubmixEffectChain.Submix, SubmixEffectChain.SubmixEffectChain, 0.1f); } } // Clear remaining submixes for (USoundSubmix* Submix : SubmixesLeftToClear) { UAudioMixerBlueprintLibrary::ClearSubmixEffectChainOverride(GetWorld(), Submix, 0.1f); } } void ULyraAudioMixEffectsSubsystem::OnLoadingScreenStatusChanged(bool bShowingLoadingScreen) { ApplyOrRemoveLoadingScreenMix(bShowingLoadingScreen); } void ULyraAudioMixEffectsSubsystem::ApplyOrRemoveLoadingScreenMix(bool bWantsLoadingScreenMix) { UWorld* World = GetWorld(); if (bAppliedLoadingScreenMix != bWantsLoadingScreenMix && LoadingScreenMix && World) { if (bWantsLoadingScreenMix) { // Apply the mix UAudioModulationStatics::ActivateBusMix(World, LoadingScreenMix); } else { // Remove the mix UAudioModulationStatics::DeactivateBusMix(World, LoadingScreenMix); } bAppliedLoadingScreenMix = bWantsLoadingScreenMix; } } bool ULyraAudioMixEffectsSubsystem::DoesSupportWorldType(const EWorldType::Type World) const { // We only need this subsystem on Game worlds (PIE included) return (World == EWorldType::Game || World == EWorldType::PIE); }