/******************************************************************************* 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. *******************************************************************************/ /*============================================================================= AkComponent.cpp: =============================================================================*/ #include "AkComponent.h" #include "AkAudioDevice.h" #include "AkAudioEvent.h" #include "AkAuxBus.h" #include "AkLateReverbComponent.h" #include "AkRoomComponent.h" #include "AkGameplayTypes.h" #include "AkSettings.h" #include "AkSpotReflector.h" #include "AkSwitchValue.h" #include "AkTrigger.h" #include "Components/BillboardComponent.h" #include "DrawDebugHelpers.h" #include "Engine/Texture2D.h" #include "Engine/World.h" #include "GameFramework/PlayerController.h" #include "Wwise/WwiseExternalSourceManager.h" #include "Wwise/API/WwiseSoundEngineAPI.h" #include "Wwise/API/WwiseSpatialAudioAPI.h" #if WITH_EDITOR #include "LevelEditorViewport.h" #include "Editor.h" #endif /*------------------------------------------------------------------------------------ Component Helpers ------------------------------------------------------------------------------------*/ namespace UAkComponentUtils { APlayerController* GetAPlayerController(const UActorComponent* Component) { const APlayerCameraManager* AsPlayerCameraManager = Cast(Component->GetOwner()); return AsPlayerCameraManager ? AsPlayerCameraManager->GetOwningPlayerController() : nullptr; } void GetListenerPosition(const UAkComponent* Component, FVector& Location, FVector& Front, FVector& Up) { APlayerController* pPlayerController = GetAPlayerController(Component); if (pPlayerController != nullptr) { FVector Right; pPlayerController->GetAudioListenerPosition(Location, Front, Right); Up = FVector::CrossProduct(Front, Right); return; } #if WITH_EDITORONLY_DATA auto& Clients = GEditor->GetAllViewportClients(); static FTransform LastKnownEditorTransform; for (int i = 0; i < Clients.Num(); i++) { FEditorViewportClient* ViewportClient = Clients[i]; UWorld* World = ViewportClient->GetWorld(); if (ViewportClient->Viewport && ViewportClient->Viewport->HasFocus() && World->AllowAudioPlayback()) { EWorldType::Type WorldType = World->WorldType; if (WorldType == EWorldType::Editor || WorldType == EWorldType::PIE) { LastKnownEditorTransform = FAkAudioDevice::Get()->GetEditorListenerPosition(i); Location = LastKnownEditorTransform.GetLocation(); Front = LastKnownEditorTransform.GetRotation().GetForwardVector(); Up = LastKnownEditorTransform.GetRotation().GetUpVector(); return; } else if (WorldType != EWorldType::Game && WorldType != EWorldType::GamePreview) { Location = ViewportClient->GetViewLocation(); Front = ViewportClient->GetViewRotation().Quaternion().GetForwardVector(); Up = ViewportClient->GetViewRotation().Quaternion().GetUpVector(); LastKnownEditorTransform.SetLocation(Location); LastKnownEditorTransform.SetRotation(ViewportClient->GetViewRotation().Quaternion()); return; } } } Location = LastKnownEditorTransform.GetLocation(); Front = LastKnownEditorTransform.GetRotation().GetForwardVector(); Up = LastKnownEditorTransform.GetRotation().GetUpVector(); #endif } void GetLocationFrontUp(const UAkComponent* Component, FVector& Location, FVector& Front, FVector& Up) { if (Component->IsDefaultListener) { GetListenerPosition(Component, Location, Front, Up); } else { auto& Transform = Component->GetComponentTransform(); Location = Transform.GetTranslation(); Front = Transform.GetUnitAxis(EAxis::X); Up = Transform.GetUnitAxis(EAxis::Z); } } } AkReverbFadeControl::AkReverbFadeControl(const UAkLateReverbComponent& LateReverbComponent) : AuxBusId(LateReverbComponent.GetAuxBusId()) , bIsFadingOut(false) , FadeControlUniqueId((void*)&LateReverbComponent) , CurrentControlValue(0.f) , TargetControlValue(LateReverbComponent.SendLevel) , FadeRate(LateReverbComponent.FadeRate) , Priority(LateReverbComponent.Priority) {} void AkReverbFadeControl::UpdateValues(const UAkLateReverbComponent& LateReverbComponent) { AuxBusId = LateReverbComponent.GetAuxBusId(); TargetControlValue = LateReverbComponent.SendLevel; FadeRate = LateReverbComponent.FadeRate; Priority = LateReverbComponent.Priority; } bool AkReverbFadeControl::Update(float DeltaTime) { if (CurrentControlValue != TargetControlValue || bIsFadingOut) { // Rate (%/s) * Delta (s) = % for given delta, apply to target. const float Increment = DeltaTime * FadeRate * TargetControlValue; if (bIsFadingOut) { CurrentControlValue -= Increment; if (CurrentControlValue <= 0.f) return false; } else CurrentControlValue = FMath::Min(CurrentControlValue + Increment, TargetControlValue); } return true; } AkAuxSendValue AkReverbFadeControl::ToAkAuxSendValue() const { AkAuxSendValue ret; ret.listenerID = AK_INVALID_GAME_OBJECT; ret.auxBusID = AuxBusId; ret.fControlValue = CurrentControlValue; return ret; } bool AkReverbFadeControl::Prioritize(const AkReverbFadeControl& A, const AkReverbFadeControl& B) { if (A.bIsFadingOut == B.bIsFadingOut) { if (A.Priority == B.Priority) { // Sort by bus id if priority and fade are equal, to ensure comparisons in UAkComponent::NeedToUpdateAuxSends dont lead to continuous aux sends updates, when there are overlapping reverbs. return A.AuxBusId < B.AuxBusId; } return A.Priority > B.Priority; } // Ensure the fading out buffers are sent to the end of the array. return A.bIsFadingOut < B.bIsFadingOut; } /*------------------------------------------------------------------------------------ UAkComponent ------------------------------------------------------------------------------------*/ UAkComponent::UAkComponent(const class FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Property initialization DrawFirstOrderReflections = false; DrawSecondOrderReflections = false; DrawHigherOrderReflections = false; DrawDiffraction = false; EarlyReflectionBusSendGain = 1.f; StopWhenOwnerDestroyed = true; bUseReverbVolumes = true; OcclusionRefreshInterval = 0.2f; PrimaryComponentTick.bCanEverTick = true; PrimaryComponentTick.TickGroup = TG_DuringPhysics; PrimaryComponentTick.bAllowTickOnDedicatedServer = false; bTickInEditor = true; bAutoActivate = true; bNeverNeedsRenderUpdate = true; bWantsOnUpdateTransform = true; #if WITH_EDITORONLY_DATA bVisualizeComponent = true; #endif AttenuationScalingFactor = 1.0f; bAutoDestroy = false; bUseDefaultListeners = true; OcclusionCollisionChannel = EAkCollisionChannel::EAKCC_UseIntegrationSettingsDefault; outerRadius = 0.0f; innerRadius = 0.0f; } ECollisionChannel UAkComponent::GetOcclusionCollisionChannel() { return UAkSettings::ConvertOcclusionCollisionChannel(OcclusionCollisionChannel.GetValue()); } void UAkComponent::PostAssociatedAkEventAndWaitForEndAsync(int32& PlayingID, FLatentActionInfo LatentInfo) { PostAkEventAndWaitForEndAsync(AkAudioEvent, PlayingID, LatentInfo); } int32 UAkComponent::PostAssociatedAkEventAndWaitForEnd(FLatentActionInfo LatentInfo) { return PostAkEventAndWaitForEnd(AkAudioEvent, EventName, LatentInfo); } AkPlayingID UAkComponent::PostAkEventByNameWithDelegate(UAkAudioEvent* AkEvent, const FString& in_EventName, int32 CallbackMask, const FOnAkPostEventCallback& PostEventCallback) { AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; if (AkEvent) { return AkEvent->PostOnComponent(this, PostEventCallback, CallbackMask, StopWhenOwnerDestroyed); } auto AudioDevice = FAkAudioDevice::Get(); if (AudioDevice) { const AkUInt32 ShortID = AudioDevice->GetShortID(AkEvent, in_EventName); PlayingID = AudioDevice->PostEventOnAkGameObject(ShortID, this, PostEventCallback, CallbackMask, {}); } return PlayingID; } AkPlayingID UAkComponent::PostAkEventByIdWithCallback(const AkUInt32 EventShortID, AkUInt32 Flags /*= 0*/, AkCallbackFunc UserCallback/*= NULL*/, void * UserCookie /*= NULL*/, const TArray& ExternalSources) { AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; auto AudioDevice = FAkAudioDevice::Get(); if (AudioDevice) { PlayingID = AudioDevice->PostEventOnAkComponent(EventShortID, this, Flags, UserCallback, UserCookie, ExternalSources); } return PlayingID; } int32 UAkComponent::PostAkEventAndWaitForEnd(class UAkAudioEvent * AkEvent, const FString& in_EventName, FLatentActionInfo LatentInfo) { if (LIKELY(!AkEvent)) { return AkEvent->PostOnComponentAndWait(this, StopWhenOwnerDestroyed, LatentInfo); } AkPlayingID PlayingID = AK_INVALID_PLAYING_ID; auto AudioDevice = FAkAudioDevice::Get(); if (AudioDevice) { const AkUInt32 ShortID = AudioDevice->GetShortID(AkEvent, in_EventName); auto* World = GetWorld(); if (UNLIKELY(!World)) { UE_LOG(LogAkAudio, Log, TEXT("Failed to post latent AkAudioEvent with actor '%s' world that's not valid."), *GetName()); return AK_INVALID_PLAYING_ID; } FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); FWaitEndOfEventAction* LatentAction = new FWaitEndOfEventAction(LatentInfo); LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, LatentAction); PlayingID = AudioDevice->PostEventOnComponentWithLatentAction(ShortID, this, LatentAction); if (PlayingID == AK_INVALID_PLAYING_ID) { LatentAction->EventFinished = true; } } return PlayingID; } void UAkComponent::PostAkEventAndWaitForEndAsync(UAkAudioEvent* AkEvent, int32& PlayingID, FLatentActionInfo LatentInfo) { if (!AkEvent) { UE_LOG(LogAkAudio, Warning, TEXT("UAkComponent::PostAkEventAndWaitForEnd: No Event specified!")); PlayingID = AK_INVALID_PLAYING_ID; return; } PlayingID = AkEvent->PostOnComponentAndWait(this, StopWhenOwnerDestroyed, LatentInfo); } int32 UAkComponent::PostAkEvent(UAkAudioEvent* AkEvent, int32 CallbackMask, const FOnAkPostEventCallback& PostEventCallback, const FString& InEventName) { if (LIKELY(IsValid(AkEvent))) { return AkEvent->PostOnComponent(this, PostEventCallback, CallbackMask, StopWhenOwnerDestroyed); } AkPlayingID playingID = AK_INVALID_PLAYING_ID; auto AudioDevice = FAkAudioDevice::Get(); if (AudioDevice) { playingID = AudioDevice->PostEventOnAkGameObject(AudioDevice->GetShortID(AkEvent, InEventName), this, PostEventCallback, CallbackMask, {}); if (playingID != AK_INVALID_PLAYING_ID) { bEventPosted = true; } } return playingID; } AkPlayingID UAkComponent::PostAkEvent(UAkAudioEvent* AkEvent, AkUInt32 Flags, AkCallbackFunc UserCallback, void* UserCookie) { if (UNLIKELY(!IsValid(AkEvent))) { UE_LOG(LogAkAudio, Error, TEXT("Failed to post invalid AkAudioEvent on component '%s'."), *GetName()); return AK_INVALID_PLAYING_ID; } return AkEvent->PostOnComponent(this, nullptr, UserCallback, UserCookie, static_cast(Flags), nullptr, StopWhenOwnerDestroyed); } AkRoomID UAkComponent::GetSpatialAudioRoom() const { AkRoomID RoomID; if (CurrentRoom) { RoomID = CurrentRoom->GetRoomID(); } return RoomID; } void UAkComponent::PostTrigger(const UAkTrigger* TriggerValue, FString Trigger) { if (FAkAudioDevice::Get()) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; if (TriggerValue) { SoundEngine->PostTrigger(TriggerValue->TriggerCookedData.TriggerId, GetAkGameObjectID()); } else { SoundEngine->PostTrigger(TCHAR_TO_AK(*Trigger), GetAkGameObjectID()); } } } void UAkComponent::SetSwitch(const UAkSwitchValue* SwitchValue, FString SwitchGroup, FString SwitchState) { if (FAkAudioDevice::Get()) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; if (SwitchValue) { SoundEngine->SetSwitch(SwitchValue->GroupValueCookedData.GroupId, SwitchValue->GroupValueCookedData.Id, GetAkGameObjectID()); } else { uint32 SwitchGroupID = SoundEngine->GetIDFromString(TCHAR_TO_AK(*SwitchGroup)); uint32 SwitchStateID = SoundEngine->GetIDFromString(TCHAR_TO_AK(*SwitchState)); SoundEngine->SetSwitch(SwitchGroupID, SwitchStateID, GetAkGameObjectID()); } } } void UAkComponent::SetStopWhenOwnerDestroyed(bool bStopWhenOwnerDestroyed) { StopWhenOwnerDestroyed = bStopWhenOwnerDestroyed; } void UAkComponent::SetListeners(const TArray& NewListeners) { auto AudioDevice = FAkAudioDevice::Get(); if (AudioDevice) { if (!bUseDefaultListeners) { for (auto Listener : Listeners) { Listener->Emitters.Remove(this); } } bUseDefaultListeners = false; Listeners.Reset(); Listeners.Append(NewListeners); for (auto Listener : Listeners) { Listener->Emitters.Add(this); } AudioDevice->SetListeners(this, Listeners.Array()); } } void UAkComponent::UseReverbVolumes(bool inUseReverbVolumes) { bUseReverbVolumes = inUseReverbVolumes; } void UAkComponent::UseEarlyReflections( class UAkAuxBus* AuxBus, int Order, float BusSendGain, float MaxPathLength, bool SpotReflectors, const FString& AuxBusName) { // Deprecated } void UAkComponent::SetEarlyReflectionsAuxBus(const FString& AuxBusName) { FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); if (AudioDevice) { AudioDevice->SetEarlyReflectionsAuxBus(this, FAkAudioDevice::GetShortID(nullptr, AuxBusName)); } } void UAkComponent::SetEarlyReflectionsVolume(float SendVolume) { FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); if (AudioDevice) { AudioDevice->SetEarlyReflectionsVolume(this, SendVolume); } } float UAkComponent::GetAttenuationRadius() const { return AkAudioEvent ? AttenuationScalingFactor * AkAudioEvent->MaxAttenuationRadius : 0.f; } void UAkComponent::SetOutputBusVolume(float BusVolume) { FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); if (AudioDevice) { for (auto It = Listeners.CreateIterator(); It; ++It) { AudioDevice->SetGameObjectOutputBusVolume(this, *It, BusVolume); } } } void UAkComponent::OnRegister() { UWorld* CurrentWorld = GetWorld(); if(!IsRegisteredWithWwise && CurrentWorld->WorldType != EWorldType::Inactive && CurrentWorld->WorldType != EWorldType::None) RegisterGameObject(); // Done before parent so that OnUpdateTransform follows registration and updates position correctly. ObstructionService.Init(this, OcclusionRefreshInterval); // It's possible for OnRegister to be called while the WorldType is inactive. // The game object will be registered again later when the WorldType is active. FAkAudioDevice * AudioDevice = FAkAudioDevice::Get(); if (AudioDevice && IsRegisteredWithWwise) { if (EarlyReflectionAuxBus || !EarlyReflectionAuxBusName.IsEmpty()) { AkUInt32 AuxBusID = FAkAudioDevice::GetShortID(EarlyReflectionAuxBus, EarlyReflectionAuxBusName); if (AuxBusID != AK_INVALID_UNIQUE_ID) AudioDevice->SetEarlyReflectionsAuxBus(this, AuxBusID); } if (EarlyReflectionBusSendGain != 1.0) AudioDevice->SetEarlyReflectionsVolume(this, EarlyReflectionBusSendGain); } Super::OnRegister(); #if WITH_EDITORONLY_DATA UpdateSpriteTexture(); #endif } #if WITH_EDITORONLY_DATA void UAkComponent::UpdateSpriteTexture() { if (SpriteComponent) { SpriteComponent->SetSprite(LoadObject(NULL, TEXT("/Wwise/S_AkComponent.S_AkComponent"))); } } #endif void UAkComponent::OnUnregister() { // Route OnUnregister event. Super::OnUnregister(); // Don't stop audio and clean up component if owner has been destroyed (default behaviour). This function gets // called from AActor::ClearComponents when an actor gets destroyed which is not usually what we want for one- // shot sounds. AActor* Owner = GetOwner(); UWorld* CurrentWorld = GetWorld(); if( !Owner || !CurrentWorld || StopWhenOwnerDestroyed || CurrentWorld->bIsTearingDown || (Owner->GetClass() == APlayerController::StaticClass() && CurrentWorld->WorldType == EWorldType::PIE)) { Stop(); } } void UAkComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) { UnregisterGameObject(); Super::OnComponentDestroyed(bDestroyingHierarchy); } void UAkComponent::ShutdownAfterError( void ) { UnregisterGameObject(); Super::ShutdownAfterError(); } bool UAkComponent::NeedToUpdateAuxSends(const TArray& NewValues) { if (NewValues.Num() != CurrentAuxSendValues.Num()) return true; for (int32 i = 0; i < NewValues.Num(); i++) { if (NewValues[i].listenerID != CurrentAuxSendValues[i].listenerID || NewValues[i].auxBusID != CurrentAuxSendValues[i].auxBusID || NewValues[i].fControlValue != CurrentAuxSendValues[i].fControlValue) { return true; } } return false; } void UAkComponent::ApplyAkReverbVolumeList(float DeltaTime) { for (int32 Idx = 0; Idx < ReverbFadeControls.Num(); ) { if (!ReverbFadeControls[Idx].Update(DeltaTime)) ReverbFadeControls.RemoveAt(Idx); else ++Idx; } if (ReverbFadeControls.Num() > 1) ReverbFadeControls.Sort(AkReverbFadeControl::Prioritize); FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); if (AkAudioDevice) { TArray NewAuxSendValues; for (int32 Idx = 0; Idx < ReverbFadeControls.Num() && Idx < AkAudioDevice->GetMaxAuxBus(); Idx++) { AkAuxSendValue* FoundAuxSend = NewAuxSendValues.FindByPredicate([=](const AkAuxSendValue& ItemInArray) { return ItemInArray.auxBusID == ReverbFadeControls[Idx].AuxBusId; }); if (FoundAuxSend) { FoundAuxSend->fControlValue += ReverbFadeControls[Idx].ToAkAuxSendValue().fControlValue; } else { NewAuxSendValues.Add(ReverbFadeControls[Idx].ToAkAuxSendValue()); } } if (NeedToUpdateAuxSends(NewAuxSendValues)) { AkAudioDevice->SetAuxSends(this, NewAuxSendValues); CurrentAuxSendValues = NewAuxSendValues; } } } void UAkComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { auto* SoundEngine = IWwiseSoundEngineAPI::Get(); if (UNLIKELY(!SoundEngine)) return; if (SoundEngine->IsInitialized()) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); // If we're a listener, update our position here instead of in OnUpdateTransform. // This is because PlayerController->GetAudioListenerPosition caches its value, and it can be out of sync if (IsDefaultListener && HasMoved()) UpdateGameObjectPosition(); if (AkAudioDevice && AkAudioDevice->WorldSpatialAudioVolumesUpdated(GetWorld())) { UpdateSpatialAudioRoom(GetComponentLocation()); // Find and apply all AkReverbVolumes at this location if (bUseReverbVolumes && AkAudioDevice->GetMaxAuxBus() > 0) { UpdateAkLateReverbComponentList(GetComponentLocation()); } } if (AkAudioDevice && bUseReverbVolumes && AkAudioDevice->GetMaxAuxBus() > 0) ApplyAkReverbVolumeList(DeltaTime); ObstructionService.Tick(Listeners, GetPosition(), GetOwner(), GetSpatialAudioRoom(), GetOcclusionCollisionChannel(), DeltaTime, OcclusionRefreshInterval); if (bAutoDestroy && bEventPosted && !HasActiveEvents()) { DestroyComponent(); } #if !UE_BUILD_SHIPPING if (DrawFirstOrderReflections || DrawSecondOrderReflections || DrawHigherOrderReflections) { DebugDrawReflections(); } if (DrawDiffraction) { DebugDrawDiffraction(); } #endif } } void UAkComponent::BeginPlay() { Super::BeginPlay(); UpdateGameObjectPosition(); // If spawned inside AkReverbVolume(s), we do not want the fade in effect to kick in. UpdateAkLateReverbComponentList(GetComponentLocation()); for (auto& ReverbFadeControl : ReverbFadeControls) ReverbFadeControl.ForceCurrentToTargetValue(); SetAttenuationScalingFactor(AttenuationScalingFactor); if (EnableSpotReflectors) AAkSpotReflector::UpdateSpotReflectors(this); } void UAkComponent::SetAttenuationScalingFactor(float Value) { AttenuationScalingFactor = Value; FAkAudioDevice* AudioDevice = FAkAudioDevice::Get(); if (AudioDevice) AudioDevice->SetAttenuationScalingFactor(this, AttenuationScalingFactor); } void UAkComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) { Super::OnUpdateTransform(UpdateTransformFlags, Teleport); // If we're a listener, our position will be updated from Tick instead of here. // This is because PlayerController->GetAudioListenerPosition caches its value, and it can be out of sync if(!IsDefaultListener) UpdateGameObjectPosition(); } UAkComponent* UAkComponent::GetAkComponent(AkGameObjectID GameObjectID) { return GameObjectID == DUMMY_GAMEOBJ ? nullptr : (UAkComponent*)GameObjectID; } void UAkComponent::GetAkGameObjectName(FString& Name) const { AActor* parentActor = GetOwner(); if (parentActor) { #if WITH_EDITOR Name = parentActor->GetActorLabel() + "."; #else Name = parentActor->GetName() + "."; #endif } Name += GetName(); UWorld* CurrentWorld = GetWorld(); switch (CurrentWorld->WorldType) { case EWorldType::Editor: Name += "(Editor)"; break; case EWorldType::EditorPreview: Name += "(EditorPreview)"; break; case EWorldType::GamePreview: Name += "(GamePreview)"; break; case EWorldType::Inactive: Name += "(Inactive)"; break; } } void UAkComponent::PostRegisterGameObject() {} void UAkComponent::PostUnregisterGameObject() {} void UAkComponent::RegisterGameObject() { FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); if ( AkAudioDevice ) { if ( bUseDefaultListeners ) { const auto& DefaultListeners = AkAudioDevice->GetDefaultListeners(); Listeners.Empty(DefaultListeners.Num()); for (auto Listener : DefaultListeners) { Listeners.Add(Listener); // NOTE: We do not add this to Listener's emitter list, the list is only for user specified (non-default) emitters. } } AkAudioDevice->RegisterComponent(this); IsRegisteredWithWwise = true; AkAudioDevice->SetGameObjectRadius(this, outerRadius, innerRadius); } PostRegisterGameObject(); } void UAkComponent::UnregisterGameObject() { FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); if (AkAudioDevice) { AkAudioDevice->UnregisterComponent(this); IsRegisteredWithWwise = false; } for (auto Listener : Listeners) Listener->Emitters.Remove(this); for (auto Emitter : Emitters) Emitter->Listeners.Remove(this); PostUnregisterGameObject(); } void UAkComponent::UpdateAkLateReverbComponentList( FVector Loc ) { FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); if (!AkAudioDevice) return; TArray FoundComponents = AkAudioDevice->FindLateReverbComponentsAtLocation(Loc, GetWorld()); // Add the new volumes to the current list for (const auto& LateReverbComponent : FoundComponents) { const auto AuxBusId = LateReverbComponent->GetAuxBusId(); const int32 FoundIdx = ReverbFadeControls.IndexOfByPredicate([=](const AkReverbFadeControl& Candidate) { return Candidate.FadeControlUniqueId == (void*)LateReverbComponent; }); if (FoundIdx == INDEX_NONE) { // The volume was not found, add it to the list ReverbFadeControls.Add(AkReverbFadeControl(*LateReverbComponent)); } else { // The volume was found. We still have to check if it is currently fading out, in case we are // getting back in a volume we just exited. ReverbFadeControls[FoundIdx].bIsFadingOut = false; // We need to update the late reverb values in case they have changed on the reverb component. ReverbFadeControls[FoundIdx].UpdateValues(*LateReverbComponent); } } // Fade out the current volumes not found in the new list for (auto& ReverbFadeControl : ReverbFadeControls) { const int32 FoundIdx = FoundComponents.IndexOfByPredicate([=](const UAkLateReverbComponent* const Candidate) { return ReverbFadeControl.FadeControlUniqueId == (void*)Candidate; }); if (FoundIdx == INDEX_NONE) ReverbFadeControl.bIsFadingOut = true; } } FVector UAkComponent::GetPosition() const { return FAkAudioDevice::AKVector64ToFVector(CurrentSoundPosition.Position()); } bool UAkComponent::HasMoved() { AkSoundPosition soundpos; FVector Location, Front, Up; UAkComponentUtils::GetLocationFrontUp(this, Location, Front, Up); FAkAudioDevice::FVectorsToAKWorldTransform(Location, Front, Up, soundpos); return CurrentSoundPosition.Position().X != soundpos.Position().X || CurrentSoundPosition.Position().Y != soundpos.Position().Y || CurrentSoundPosition.Position().Z != soundpos.Position().Z || CurrentSoundPosition.OrientationTop().X != soundpos.OrientationTop().X || CurrentSoundPosition.OrientationTop().Y != soundpos.OrientationTop().Y || CurrentSoundPosition.OrientationTop().Z != soundpos.OrientationTop().Z || CurrentSoundPosition.OrientationFront().X != soundpos.OrientationFront().X || CurrentSoundPosition.OrientationFront().Y != soundpos.OrientationFront().Y || CurrentSoundPosition.OrientationFront().Z != soundpos.OrientationFront().Z; } void UAkComponent::UpdateGameObjectPosition() { #ifdef _DEBUG CheckEmitterListenerConsistancy(); #endif FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); if (IsActive() && AkAudioDevice) { if (AllowAudioPlayback()) { AkSoundPosition soundpos; FVector Location, Front, Up; UAkComponentUtils::GetLocationFrontUp(this, Location, Front, Up); FAkAudioDevice::FVectorsToAKWorldTransform(Location, Front, Up, soundpos); UpdateSpatialAudioRoom(Location); AkAudioDevice->SetPosition(this, soundpos); CurrentSoundPosition = soundpos; } // Find and apply all AkReverbVolumes at this location if (bUseReverbVolumes && AkAudioDevice->GetMaxAuxBus() > 0) { UpdateAkLateReverbComponentList(GetComponentLocation()); } } } void UAkComponent::UpdateSpatialAudioRoom(FVector Location) { if (IsRegisteredWithWwise) { FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); if (AkAudioDevice) { AKRESULT result = AK_Fail; TArray RoomComponents = AkAudioDevice->FindRoomComponentsAtLocation(Location, GetWorld()); if (RoomComponents.Num() == 0) { if (AkAudioDevice->WorldHasActiveRooms(GetWorld()) && CurrentRoom != nullptr) { CurrentRoom = nullptr; result = AkAudioDevice->SetInSpatialAudioRoom(GetAkGameObjectID(), GetSpatialAudioRoom()); } } else if (CurrentRoom != RoomComponents[0]) { CurrentRoom = RoomComponents[0]; result = AkAudioDevice->SetInSpatialAudioRoom(GetAkGameObjectID(), GetSpatialAudioRoom()); } if (EnableSpotReflectors && result == AK_Success) AAkSpotReflector::UpdateSpotReflectors(this); } } } const TSet& UAkComponent::GetEmitters() { FAkAudioDevice* Device = FAkAudioDevice::Get(); if (Device) { auto DefaultListeners = Device->GetDefaultListeners(); if (DefaultListeners.Contains(this)) return Device->GetDefaultEmitters(); else return Emitters; } return Emitters; } void UAkComponent::CheckEmitterListenerConsistancy() { for (auto Emitter : GetEmitters()) { check(Emitter->Listeners.Contains(this)); } for (auto Listener : Listeners) { check(Listener->GetEmitters().Contains(this)); } } void UAkComponent::_DebugDrawReflections( const AkVector64& akEmitterPos, const AkVector64& akListenerPos, const AkReflectionPathInfo* paths, AkUInt32 uNumPaths) const { ::FlushDebugStrings(GWorld); for (AkInt32 idxPath = uNumPaths-1; idxPath >= 0; --idxPath) { const AkReflectionPathInfo& path = paths[idxPath]; unsigned int order = path.numReflections; if ((DrawFirstOrderReflections && order == 1) || (DrawSecondOrderReflections && order == 2) || (DrawHigherOrderReflections && order > 2)) { FColor colorLight; FColor colorMed; FColor colorDark; switch ((order - 1)) { case 0: colorLight = FColor(0x9DEBF3); colorMed = FColor(0x318087); colorDark = FColor(0x186067); break; case 1: colorLight = FColor(0xFCDBA2); colorMed = FColor(0xDEAB4E); colorDark = FColor(0xA97B27); break; case 2: default: colorLight = FColor(0xFCB1A2); colorMed = FColor(0xDE674E); colorDark = FColor(0xA93E27); break; } FColor colorLightGrey(75, 75, 75); FColor colorMedGrey(50, 50, 50); FColor colorDarkGrey(35, 35, 35); const int kPathThickness = 5.f; const float kRadiusSphere = 25.f; const int kNumSphereSegments = 8; const FVector emitterPos = FAkAudioDevice::AKVector64ToFVector(akEmitterPos); FVector listenerPt = FAkAudioDevice::AKVector64ToFVector(akListenerPos); for (int idxSeg = path.numPathPoints-1; idxSeg >= 0; --idxSeg) { const FVector reflectionPt = FAkAudioDevice::AKVector64ToFVector(path.pathPoint[idxSeg]); if (idxSeg != path.numPathPoints - 1) { // Note: Not drawing the first leg of the path from the listener. Often hard to see because it is typically the camera position. ::DrawDebugLine(GWorld, listenerPt, reflectionPt, path.isOccluded ? colorLightGrey : colorLight, false, -1.f, (uint8)'\000', kPathThickness / order); ::DrawDebugSphere(GWorld, reflectionPt, (kRadiusSphere/2) / order, kNumSphereSegments, path.isOccluded ? colorLightGrey : colorLight); } else { ::DrawDebugSphere(GWorld, reflectionPt, kRadiusSphere / order, kNumSphereSegments, path.isOccluded ? colorMedGrey : colorMed); } // Draw image source point. Not as useful as I had hoped. //const FVector imageSrc = FAkAudioDevice::AKVectorToFVector(path.imageSource); //::DrawDebugSphere(GWorld, imageSrc, kRadiusSphere/order, kNumSphereSegments, colorDark); listenerPt = reflectionPt; } if (!path.isOccluded) { // Finally the last path segment towards the emitter. ::DrawDebugLine(GWorld, listenerPt, emitterPos, path.isOccluded ? colorLightGrey : colorLight, false, -1.f, (uint8)'\000', kPathThickness / order); } } } } void UAkComponent::_DebugDrawDiffraction(const AkVector64& akEmitterPos, const AkVector64& akListenerPos, const AkDiffractionPathInfo* paths, AkUInt32 uNumPaths) const { ::FlushDebugStrings(GWorld); for (AkInt32 idxPath = uNumPaths - 1; idxPath >= 0; --idxPath) { const AkDiffractionPathInfo& path = paths[idxPath]; FColor purple(0x492E74); FColor green(0x267158); if (path.nodeCount > 0) { const int kPathThickness = 5.f; const float kRadiusSphereMax = 35.f; const float kRadiusSphereMin = 2.f; const FVector emitterPos = FAkAudioDevice::AKVector64ToFVector(akEmitterPos); const FVector listenerPos = FAkAudioDevice::AKVector64ToFVector(akListenerPos); FVector prevPt = FAkAudioDevice::AKVector64ToFVector(akListenerPos); for (int idxSeg = 0; idxSeg < (int)path.nodeCount; ++idxSeg) { const FVector pt = FAkAudioDevice::AKVector64ToFVector(path.nodes[idxSeg]); if (idxSeg != 0) { ::DrawDebugLine(GWorld, prevPt, pt, green, false, -1.f, (uint8)'\000', kPathThickness); } float rad = kRadiusSphereMin + (1.f - path.angles[idxSeg] / PI) * (kRadiusSphereMax - kRadiusSphereMin); ::DrawDebugSphere(GWorld, pt, rad, 8, path.portals[idxSeg].IsValid() ? green : purple ); prevPt = pt; } // Finally the last path segment towards the emitter. ::DrawDebugLine(GWorld, prevPt, emitterPos, green, false, -1.f, (uint8)'\000', kPathThickness); } } } void UAkComponent::DebugDrawReflections() const { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return; enum { kMaxPaths = 64 }; AkReflectionPathInfo paths[kMaxPaths]; AkUInt32 uNumPaths = kMaxPaths; AkVector64 listenerPos, emitterPos; if (SpatialAudio->QueryReflectionPaths(GetAkGameObjectID(), 0, listenerPos, emitterPos, paths, uNumPaths) == AK_Success && uNumPaths > 0) _DebugDrawReflections(emitterPos, listenerPos, paths, uNumPaths); } void UAkComponent::DebugDrawDiffraction() const { auto* SpatialAudio = IWwiseSpatialAudioAPI::Get(); if (UNLIKELY(!SpatialAudio)) return; enum { kMaxPaths = 16 }; AkDiffractionPathInfo paths[kMaxPaths]; AkUInt32 uNumPaths = kMaxPaths; AkVector64 listenerPos, emitterPos; if (SpatialAudio->QueryDiffractionPaths(GetAkGameObjectID(), 0, listenerPos, emitterPos, paths, uNumPaths) == AK_Success) { if (uNumPaths > 0) _DebugDrawDiffraction(emitterPos, listenerPos, paths, uNumPaths); } } void UAkComponent::SetGameObjectRadius(float in_outerRadius, float in_innerRadius) { outerRadius = in_outerRadius; innerRadius = in_innerRadius; FAkAudioDevice* AkAudioDevice = FAkAudioDevice::Get(); if (AkAudioDevice && IsRegisteredWithWwise) { AkAudioDevice->SetGameObjectRadius(this, outerRadius, innerRadius); } } void UAkComponent::SetEnableSpotReflectors(bool in_enable) { if (EnableSpotReflectors != in_enable) { EnableSpotReflectors = in_enable; AAkSpotReflector::UpdateSpotReflectors(this); } } #if WITH_EDITOR void UAkComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); if (PropertyChangedEvent.Property) { if ((PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAkComponent, outerRadius) || PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAkComponent, innerRadius)) && PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet) { if (innerRadius > outerRadius) innerRadius = outerRadius; SetGameObjectRadius(outerRadius, innerRadius); } if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAkComponent, EnableSpotReflectors) && PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet) { AAkSpotReflector::UpdateSpotReflectors(this); } } } #endif