From ddcd2775db4ceb78e4d1ea7f6738ff3ff9deccb9 Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Thu, 8 Oct 2026 14:01:05 +0000 Subject: [PATCH 01/12] create the AudRoom class to wrap around AkRoom --- CMakeLists.txt | 3 ++ src/AudGameObjResource.cpp | 7 ++-- src/AudRoom.cpp | 59 +++++++++++++++++++++++++++++ src/AudRoom.h | 76 ++++++++++++++++++++++++++++++++++++++ src/AudRoom_Blue.cpp | 30 +++++++++++++++ src/Audio2.h | 4 ++ 6 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 src/AudRoom.cpp create mode 100644 src/AudRoom.h create mode 100644 src/AudRoom_Blue.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 405df2d..bf72552 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,8 @@ ccp_add_library(CarbonAudio SHARED src/AudGeometry.cpp src/AudGeometry_Blue.cpp src/AudObstructionOcclusion.cpp + src/AudRoom.cpp + src/AudRoom_Blue.cpp src/SpatialAudioSettings.cpp src/audio2.cpp src/AudGameObjResource_Blue.cpp @@ -86,6 +88,7 @@ target_sources(CarbonAudio PRIVATE src/AudMusicPlayer.h src/AudGeometry.h src/AudObstructionOcclusion.h + src/AudRoom.h src/SpatialAudioSettings.h src/autoversion.h src/DebugUtilities.h diff --git a/src/AudGameObjResource.cpp b/src/AudGameObjResource.cpp index a38f13e..f742065 100644 --- a/src/AudGameObjResource.cpp +++ b/src/AudGameObjResource.cpp @@ -10,9 +10,10 @@ #include "SoundPrioritization.h" //----------------------------------------------------------------------------- -// Helper function to distribute ID's +// Helper function to distribute ID's. Shared with AudRoom so room IDs never +// collide with game object IDs. //----------------------------------------------------------------------------- -static AkGameObjectID GenerateEntityID() +AkGameObjectID AllocateGameObjectID() { static AkGameObjectID s_currentID = START_GAME_OBJ_COUNT; return s_currentID++; @@ -49,7 +50,7 @@ AudGameObjResource::AudGameObjResource( IRoot* lockobj ) : PARENTLOCK( m_paramet m_waitingOneShotInRange( std::pair( std::chrono::steady_clock::now(), L"" ) ), m_eventName(L"") { - m_ID = GenerateEntityID(); + m_ID = AllocateGameObjectID(); if (g_audioManager != nullptr) { diff --git a/src/AudRoom.cpp b/src/AudRoom.cpp new file mode 100644 index 0000000..cd0c580 --- /dev/null +++ b/src/AudRoom.cpp @@ -0,0 +1,59 @@ +//////////////////////////////////////////////////////////// +// +// Creator: Phevos Rinis +// Creation Date: Sep 2026 +// Copyright (c) 2026 CCP Games +// + +#include "stdafx.h" +#include "AudRoom.h" + +#include "Audio2.h" + +AudRoom::AudRoom( IRoot* lockobj ) : + m_roomID( AllocateGameObjectID() ), + m_reverbLevel( 1.0f ), + m_transmissionLoss( 1.0f ), + m_priority( 100.0f ), + m_auxSendLevelToSelf( 0.0f ), + m_keepRegistered( false ), + m_enabled( true ), + m_shapeEnabled( true ), + m_unitBoxToWorld( IdentityMatrix() ), + m_shapeValid( false ), + m_warnedDegenerate( false ), + m_hasTransform( false ) +{ +} + +AudRoom::~AudRoom() +{ +} + +void AudRoom::SetTransform( const Matrix& unitBoxToWorld ) +{ + m_unitBoxToWorld = unitBoxToWorld; + m_hasTransform = true; + + // A box with a zero-length axis (e.g. a freshly placed, unscaled volume) must not become a 1 m cube in Wwise. + const Matrix& m = m_unitBoxToWorld; + constexpr float minAxisLengthSq = 1e-6f; // 1 mm + m_shapeValid = LengthSq( Vector3( m._11, m._12, m._13 ) ) > minAxisLengthSq + && LengthSq( Vector3( m._21, m._22, m._23 ) ) > minAxisLengthSq + && LengthSq( Vector3( m._31, m._32, m._33 ) ) > minAxisLengthSq; + if( !m_shapeValid && !m_warnedDegenerate ) + { + CCP_LOGWARN( "Room '%s' (%llu) has a box with a zero-length axis; it is not sent to Wwise until it is scaled.", m_name.c_str(), m_roomID ); + m_warnedDegenerate = true; + } +} + +void AudRoom::SetEnabled( bool enabled ) +{ + m_shapeEnabled = enabled; +} + +void AudRoom::Remove() +{ + m_hasTransform = false; +} diff --git a/src/AudRoom.h b/src/AudRoom.h new file mode 100644 index 0000000..be7308c --- /dev/null +++ b/src/AudRoom.h @@ -0,0 +1,76 @@ +//////////////////////////////////////////////////////////// +// +// Creator: Phevos Rinis +// Creation Date: Sep 2026 +// Copyright (c) 2026 CCP Games +// + +#pragma once + +#include + +#include + +#include + +/** + * @brief A Wwise Spatial Audio room described by a unit box placed in the world. + * + * Implements ITr2VolumeObject so that trinity can place the box without knowing anything about + * acoustics. Everything acoustic (reverb aux bus, priority, transmission loss) lives here as + * persisted attributes, the same split used between EveChildAudio and AudEmitter. + */ +BLUE_CLASS( AudRoom ) : + public ITr2VolumeObject +{ +public: + AudRoom( IRoot* lockobj = NULL ); + virtual ~AudRoom(); + + EXPOSE_TO_BLUE(); + + // ITr2VolumeObject + void SetTransform( const Matrix& unitBoxToWorld ) override; + void SetEnabled( bool enabled ) override; + void Remove() override; + + /// Room identifier. Shares the Wwise game object ID space, see AkRoomID. + AkUInt64 GetRoomID() const { return m_roomID; } + /// Name shown in the Wwise profiler. + const std::string& GetName() const { return m_name; } + /// Wwise aux bus name used for the room reverb. Empty means no reverb. + const std::string& GetReverbAuxBus() const { return m_reverbAuxBus; } + float GetReverbLevel() const { return m_reverbLevel; } + float GetTransmissionLoss() const { return m_transmissionLoss; } + float GetPriority() const { return m_priority; } + float GetAuxSendLevelToSelf() const { return m_auxSendLevelToSelf; } + bool GetKeepRegistered() const { return m_keepRegistered; } + /// Active only when both the authored attribute and the shape owner (trinity) say so. + bool IsEnabled() const { return m_enabled && m_shapeEnabled; } + bool HasTransform() const { return m_hasTransform; } + /// False when the box has a zero-length axis; such a room contains nothing and is not sent to Wwise. + bool HasUsableShape() const { return m_shapeValid; } + const Matrix& GetUnitBoxToWorld() const { return m_unitBoxToWorld; } + +private: + AkUInt64 m_roomID; + + std::string m_name; + std::string m_reverbAuxBus; + float m_reverbLevel; + float m_transmissionLoss; + float m_priority; + float m_auxSendLevelToSelf; + bool m_keepRegistered; + /// Authored attribute: the acoustic side of "enabled". + bool m_enabled; + /// Set through ITr2VolumeObject::SetEnabled by whoever owns the shape. Starts enabled, never persisted. + bool m_shapeEnabled; + + Matrix m_unitBoxToWorld; + bool m_shapeValid; + bool m_warnedDegenerate; + bool m_hasTransform; +}; + +TYPEDEF_BLUECLASS( AudRoom ); diff --git a/src/AudRoom_Blue.cpp b/src/AudRoom_Blue.cpp new file mode 100644 index 0000000..ec6f17e --- /dev/null +++ b/src/AudRoom_Blue.cpp @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////// +// +// Creator: Phevos Rinis +// Creation Date: Sep 2026 +// Copyright (c) 2026 CCP Games +// + +#include "stdafx.h" +#include "AudRoom.h" + +BLUE_DEFINE( AudRoom ); +BLUE_DEFINE_INTERFACE( ITr2VolumeObject ); + +const Be::ClassInfo* AudRoom::ExposeToBlue() +{ + EXPOSURE_BEGIN( AudRoom, "A Wwise Spatial Audio room. Trinity places it as a unit box through ITr2VolumeObject; the acoustic parameters live here." ) + MAP_INTERFACE( ITr2VolumeObject ) + + MAP_ATTRIBUTE( "name", m_name, "Name of the room, shown in the Wwise profiler.", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "reverbAuxBus", m_reverbAuxBus, "Name of the Wwise auxiliary bus carrying the reverb of this room. Empty for no reverb.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "reverbLevel", m_reverbLevel, "Send level [0, 1] from emitters inside the room to the reverb aux bus.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "transmissionLoss", m_transmissionLoss, "Transmission loss [0, 1] applied to sound passing through the walls of this room. 1 blocks everything.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "priority", m_priority, "When a game object is inside several rooms the room with the highest priority wins.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "auxSendLevelToSelf", m_auxSendLevelToSelf, "Send level [0, 1] of the room game object to its own reverb bus, for room tones posted on the room.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "keepRegistered", m_keepRegistered, "Keep the room game object registered in Wwise at all times, needed when posting events or RTPCs on the room.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "enabled", m_enabled, "Whether the room is acoustically active. The room is only sent to Wwise when this and the owning volume object are both enabled.", Be::READWRITE | Be::PERSIST ) + + MAP_ATTRIBUTE( "roomID", m_roomID, "Wwise room identifier.\n:jessica-hidden: True", Be::READ ) + EXPOSURE_END() +} diff --git a/src/Audio2.h b/src/Audio2.h index 37df70e..27f49f9 100644 --- a/src/Audio2.h +++ b/src/Audio2.h @@ -26,6 +26,10 @@ inline bool IsReservedGameObjectID( AkGameObjectID id ) return id < START_GAME_OBJ_COUNT; } +// Hands out the next free Wwise game object ID. Also used for Spatial Audio rooms, +// which share the game object ID space (see AkRoomID). +AkGameObjectID AllocateGameObjectID(); + // Makes sure objects are initialized far away so you don't hear them when they spawn. // Game objects are culled by default so this value will never hit Wwise. const Vector3 WWISE_INIT_POSITION = Vector3(FLT_MAX, FLT_MAX, FLT_MAX); From f2c999468e4a57c2e2298e5bbba1c48da6525bea Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Thu, 8 Oct 2026 14:03:24 +0000 Subject: [PATCH 02/12] send rooms to wwise --- CMakeLists.txt | 2 + src/AudManager.cpp | 21 ++- src/AudManager.h | 6 + src/AudRoom.cpp | 64 ++++++++- src/AudRoom.h | 20 ++- src/AudRoomManager.cpp | 306 +++++++++++++++++++++++++++++++++++++++++ src/AudRoomManager.h | 77 +++++++++++ src/AudRoom_Blue.cpp | 18 +-- 8 files changed, 502 insertions(+), 12 deletions(-) create mode 100644 src/AudRoomManager.cpp create mode 100644 src/AudRoomManager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bf72552..1631dd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ ccp_add_library(CarbonAudio SHARED src/AudObstructionOcclusion.cpp src/AudRoom.cpp src/AudRoom_Blue.cpp + src/AudRoomManager.cpp src/SpatialAudioSettings.cpp src/audio2.cpp src/AudGameObjResource_Blue.cpp @@ -89,6 +90,7 @@ target_sources(CarbonAudio PRIVATE src/AudGeometry.h src/AudObstructionOcclusion.h src/AudRoom.h + src/AudRoomManager.h src/SpatialAudioSettings.h src/autoversion.h src/DebugUtilities.h diff --git a/src/AudManager.cpp b/src/AudManager.cpp index 845f5cf..6b76300 100644 --- a/src/AudManager.cpp +++ b/src/AudManager.cpp @@ -82,6 +82,7 @@ AudManager::AudManager( IRoot* lockobj ) : m_soundPrioritization = new SoundPrioritization(); m_spatialAudioSettings = new SpatialAudioSettings(); m_obstructionOcclusion = std::make_unique( this ); + m_roomManager = std::make_unique( this ); } AudManager::~AudManager() @@ -221,6 +222,8 @@ void AudManager::Terminate() // Terminate the Memory Manager AK::MemoryMgr::Term(); + // The sound engine is gone, so is everything Spatial Audio knew about rooms. + m_roomManager->ForgetWwiseState(); m_spatialAudioGeometryInitialized = false; SetAudioState( AudioState::Uninitialized ); @@ -501,6 +504,14 @@ bool AudManager::GetSpatialAudioGeometryEnabled() const return m_spatialAudioSettings->GetSpatialAudioGeometryEnabled(); } +bool AudManager::AreRoomsReady() const +{ + return GetState() == AudioState::Enabled + && m_spatialAudioGeometryInitialized + && GetSpatialAudioGeometryEnabled() + && !g_shuttingDown; +} + void AudManager::SetSpatialAudioGeometryEnabled( bool enabled ) { const bool wasEnabled = GetSpatialAudioGeometryEnabled(); @@ -518,6 +529,8 @@ void AudManager::SetSpatialAudioGeometryEnabled( bool enabled ) if( !enabled ) { m_spatialAudioSettings->SetSpatialAudioGeometryEnabled( false ); + // Rooms run on Spatial Audio geometry, so they leave Wwise with it. + m_roomManager->RemoveAllFromWwise(); AudGeometry::ClearAllGeometry(); CCP_LOG_CH( s_ch, "Spatial audio geometry disabled." ); } @@ -530,6 +543,7 @@ void AudManager::SetSpatialAudioGeometryEnabled( bool enabled ) } m_spatialAudioSettings->SetSpatialAudioGeometryEnabled( true ); + m_roomManager->ResendAll(); CCP_LOG_CH( s_ch, "Spatial audio geometry enabled." ); } } @@ -837,7 +851,9 @@ void AudManager::Disable() ClearBanks(); m_obstructionQuery = nullptr; m_obstructionOcclusion->Reset(); - AudGeometry::ClearAllGeometry(); + // Rooms leave Wwise with audio and are re-sent by Enable(). + m_roomManager->RemoveAllFromWwise(); + AudGeometry::ClearAllGeometry(); #ifndef AK_OPTIMIZED AK::SoundEngine::UnregisterResourceMonitorCallback(ResourceMonitorCallback); #endif @@ -937,6 +953,9 @@ void AudManager::Enable( BankVector soundBanksToLoad ) obj->Wake(); } + // Rooms that were placed while audio was disabled, or removed by Disable(), go back into Wwise. + m_roomManager->ResendAll(); + BeOS->RegisterForTicks( this, (void*)"Audio::Tick" ); return; } diff --git a/src/AudManager.h b/src/AudManager.h index 88c3f70..a9b7041 100644 --- a/src/AudManager.h +++ b/src/AudManager.h @@ -10,6 +10,7 @@ #include "SoundPrioritization.h" #include "SpatialAudioSettings.h" #include "AudObstructionOcclusion.h" +#include "AudRoomManager.h" #include "LowLevelIO/LowLevelIOHook.h" #include #include @@ -124,6 +125,10 @@ BLUE_CLASS( AudManager ) : bool GetSpatialAudioGeometryEnabled() const; // Enables or disables spatial audio geometry. void SetSpatialAudioGeometryEnabled( bool enabled ); + // True when Wwise Spatial Audio is initialized, geometry is on and audio is enabled, i.e. rooms may be sent. + bool AreRoomsReady() const; + // Owns the Wwise side of every AudRoom. + AudRoomManager& GetRoomManager() { return *m_roomManager; } // Set a single line-of-sight blockage ratio for an emitter [0.0, 1.0]. 0 = clear line of sight. bool SetEmitterLineOfSightBlockage( AkGameObjectID emitterID, float blockage ); // Current, mid-fade occlusion value for an emitter. 0.0 if the emitter is clear or not tracked. @@ -274,6 +279,7 @@ BLUE_CLASS( AudManager ) : SoundPrioritization* m_soundPrioritization; SpatialAudioSettings* m_spatialAudioSettings; std::unique_ptr m_obstructionOcclusion; + std::unique_ptr m_roomManager; // Map of game objects, used to guard Wwise callbacks std::unordered_map m_callbackGameObjects; diff --git a/src/AudRoom.cpp b/src/AudRoom.cpp index cd0c580..a505741 100644 --- a/src/AudRoom.cpp +++ b/src/AudRoom.cpp @@ -8,7 +8,8 @@ #include "stdafx.h" #include "AudRoom.h" -#include "Audio2.h" +#include "AudManager.h" +#include "AudRoomManager.h" AudRoom::AudRoom( IRoot* lockobj ) : m_roomID( AllocateGameObjectID() ), @@ -22,12 +23,54 @@ AudRoom::AudRoom( IRoot* lockobj ) : m_unitBoxToWorld( IdentityMatrix() ), m_shapeValid( false ), m_warnedDegenerate( false ), - m_hasTransform( false ) + m_hasTransform( false ), + m_sentToWwise( false ), + m_registeredWithManager( false ) { + EnsureRegistered(); } AudRoom::~AudRoom() { + if( g_audioManager != nullptr && m_registeredWithManager ) + { + g_audioManager->GetRoomManager().UnregisterRoom( this ); + } +} + +bool AudRoom::EnsureRegistered() +{ + if( m_registeredWithManager ) + { + return true; + } + + if( g_audioManager == nullptr ) + { + return false; + } + + g_audioManager->GetRoomManager().RegisterRoom( this ); + m_registeredWithManager = true; + return true; +} + +void AudRoom::Sync() +{ + if( !EnsureRegistered() ) + { + return; + } + + AudRoomManager& manager = g_audioManager->GetRoomManager(); + if( IsEnabled() && m_hasTransform && m_shapeValid ) + { + manager.Push( *this ); + } + else + { + manager.Remove( *this ); + } } void AudRoom::SetTransform( const Matrix& unitBoxToWorld ) @@ -46,14 +89,31 @@ void AudRoom::SetTransform( const Matrix& unitBoxToWorld ) CCP_LOGWARN( "Room '%s' (%llu) has a box with a zero-length axis; it is not sent to Wwise until it is scaled.", m_name.c_str(), m_roomID ); m_warnedDegenerate = true; } + + // Sends the room, or removes it when the new box is degenerate. + Sync(); } void AudRoom::SetEnabled( bool enabled ) { + if( m_shapeEnabled == enabled ) + { + return; + } + m_shapeEnabled = enabled; + Sync(); } void AudRoom::Remove() { m_hasTransform = false; + Sync(); +} + +bool AudRoom::OnModified( Be::Var* value ) +{ + // Any authored attribute changed (e.g. edited in Graphite); re-send the room with the new parameters. + Sync(); + return true; } diff --git a/src/AudRoom.h b/src/AudRoom.h index be7308c..c99cf9a 100644 --- a/src/AudRoom.h +++ b/src/AudRoom.h @@ -19,9 +19,13 @@ * Implements ITr2VolumeObject so that trinity can place the box without knowing anything about * acoustics. Everything acoustic (reverb aux bus, priority, transmission loss) lives here as * persisted attributes, the same split used between EveChildAudio and AudEmitter. + * + * The room is registered in Wwise with a containment-only geometry instance of a shared unit + * cube (see AudRoomManager), which Wwise uses to place game objects in the room. */ BLUE_CLASS( AudRoom ) : - public ITr2VolumeObject + public ITr2VolumeObject, + public INotify { public: AudRoom( IRoot* lockobj = NULL ); @@ -34,6 +38,9 @@ BLUE_CLASS( AudRoom ) : void SetEnabled( bool enabled ) override; void Remove() override; + // INotify + bool OnModified( Be::Var* value ) override; + /// Room identifier. Shares the Wwise game object ID space, see AkRoomID. AkUInt64 GetRoomID() const { return m_roomID; } /// Name shown in the Wwise profiler. @@ -51,8 +58,17 @@ BLUE_CLASS( AudRoom ) : /// False when the box has a zero-length axis; such a room contains nothing and is not sent to Wwise. bool HasUsableShape() const { return m_shapeValid; } const Matrix& GetUnitBoxToWorld() const { return m_unitBoxToWorld; } + /// Whether the room currently exists in Wwise. + bool IsRegistered() const { return m_sentToWwise; } private: + friend class AudRoomManager; + + /// Registers with the room manager if that has not happened yet (the manager may not exist at construction). + bool EnsureRegistered(); + /// Sends the current state to Wwise, or removes the room if it is disabled. + void Sync(); + AkUInt64 m_roomID; std::string m_name; @@ -71,6 +87,8 @@ BLUE_CLASS( AudRoom ) : bool m_shapeValid; bool m_warnedDegenerate; bool m_hasTransform; + bool m_sentToWwise; + bool m_registeredWithManager; }; TYPEDEF_BLUECLASS( AudRoom ); diff --git a/src/AudRoomManager.cpp b/src/AudRoomManager.cpp new file mode 100644 index 0000000..bfc5ad5 --- /dev/null +++ b/src/AudRoomManager.cpp @@ -0,0 +1,306 @@ +//////////////////////////////////////////////////////////// +// +// Creator: Phevos Rinis +// Creation Date: Sep 2026 +// Copyright (c) 2026 CCP Games +// + +#include "stdafx.h" +#include "AudRoomManager.h" + +#include "AudManager.h" +#include "AudRoom.h" +#include "Utilities.h" + +#include +#include + +static CcpLogChannel_t s_ch = CCP_LOG_DEFINE_CHANNEL( "AudRoomManager" ); + +namespace +{ + // Unit cube [-0.5, 0.5]^3. Defined directly in Wwise space: the cube is symmetric under the + // right- to left-handed Z flip, and Wwise triangles are double-sided so winding does not matter. + const AkVertex CUBE_VERTICES[8] = + { + AkVertex( -0.5f, -0.5f, -0.5f ), + AkVertex( 0.5f, -0.5f, -0.5f ), + AkVertex( 0.5f, 0.5f, -0.5f ), + AkVertex( -0.5f, 0.5f, -0.5f ), + AkVertex( -0.5f, -0.5f, 0.5f ), + AkVertex( 0.5f, -0.5f, 0.5f ), + AkVertex( 0.5f, 0.5f, 0.5f ), + AkVertex( -0.5f, 0.5f, 0.5f ), + }; + + const AkTriangle CUBE_TRIANGLES[12] = + { + AkTriangle( 0, 1, 2, 0 ), AkTriangle( 0, 2, 3, 0 ), // -Z + AkTriangle( 4, 6, 5, 0 ), AkTriangle( 4, 7, 6, 0 ), // +Z + AkTriangle( 0, 5, 1, 0 ), AkTriangle( 0, 4, 5, 0 ), // -Y + AkTriangle( 3, 2, 6, 0 ), AkTriangle( 3, 6, 7, 0 ), // +Y + AkTriangle( 0, 3, 7, 0 ), AkTriangle( 0, 7, 4, 0 ), // -X + AkTriangle( 1, 5, 6, 0 ), AkTriangle( 1, 6, 2, 0 ), // +X + }; +} + +AudRoomManager::AudRoomManager( AudManager* audioManager ) : + m_audioManager( audioManager ), + m_cubeSent( false ), + m_outdoorConfigured( false ), + m_mutex( "AudRoomManager", "m_mutex" ) +{ +} + +AudRoomManager::~AudRoomManager() +{ +} + +AkUInt64 AudRoomManager::GeometryInstanceIDForRoom( const AudRoom& room ) +{ + return room.GetRoomID() | ROOM_SPATIAL_ID_TAG; +} + +void AudRoomManager::RegisterRoom( AudRoom* room ) +{ + CcpAutoMutex lock( m_mutex ); + m_rooms[room->GetRoomID()] = room; +} + +void AudRoomManager::UnregisterRoom( AudRoom* room ) +{ + CcpAutoMutex lock( m_mutex ); + RemoveLocked( *room ); + m_rooms.erase( room->GetRoomID() ); +} + +size_t AudRoomManager::GetRoomCount() const +{ + CcpAutoMutex lock( m_mutex ); + return m_rooms.size(); +} + +bool AudRoomManager::EnsureSharedCubeGeometry() +{ + if( m_cubeSent ) + { + return true; + } + + // One placeholder surface for the whole cube until rooms get acoustic materials. Containment-only geometry + // is not ray traced, so the room's own TransmissionLoss applies to direct paths; Wwise still uses room + // geometry surfaces for the transmission of reverb and room tones through the walls. + AkAcousticSurface surface; + surface.strName = "AudRoomCube"; + surface.textureID = AK_INVALID_UNIQUE_ID; + surface.transmissionLoss = 1.0f; + + AkGeometryParams params; + params.Vertices = const_cast( CUBE_VERTICES ); + params.NumVertices = static_cast( std::size( CUBE_VERTICES ) ); + params.Triangles = const_cast( CUBE_TRIANGLES ); + params.NumTriangles = static_cast( std::size( CUBE_TRIANGLES ) ); + params.Surfaces = &surface; + params.NumSurfaces = 1; + params.EnableDiffraction = false; + params.EnableDiffractionOnBoundaryEdges = false; + + AKRESULT result = AK::SpatialAudio::SetGeometry( SHARED_CUBE_GEOMETRY_SET_ID, params ); + if( result != AK_Success ) + { + CCP_LOGERR_CH( s_ch, "Failed to set the shared room geometry, AKRESULT: %d", result ); + return false; + } + + m_cubeSent = true; + return true; +} + +void AudRoomManager::ReleaseSharedCubeGeometry() +{ + if( !m_cubeSent ) + { + return; + } + + AK::SpatialAudio::RemoveGeometry( SHARED_CUBE_GEOMETRY_SET_ID ); + m_cubeSent = false; +} + +bool AudRoomManager::EnsureOutdoorRoomConfigured() +{ + if( m_outdoorConfigured ) + { + return true; + } + + // Wwise applies room transmission loss from both the emitter's and the listener's room. Give the + // outdoor room (space, station exteriors) none, so an interior's own TransmissionLoss is what is heard. + AkRoomParams params; + params.TransmissionLoss = 0.0f; + params.ReverbAuxBus = AK_INVALID_AUX_ID; + params.RoomGameObj_KeepRegistered = false; + params.GeometryInstanceID = AkGeometryInstanceID(); + + AKRESULT result = AK::SpatialAudio::SetRoom( AK::SpatialAudio::kOutdoorRoomID, params, "Outdoors" ); + if( result != AK_Success ) + { + CCP_LOGERR_CH( s_ch, "Failed to configure the outdoor room, AKRESULT: %d", result ); + return false; + } + + m_outdoorConfigured = true; + return true; +} + +void AudRoomManager::PushLocked( AudRoom& room ) +{ + if( !room.IsEnabled() || !room.HasTransform() || !room.HasUsableShape() ) + { + RemoveLocked( room ); + return; + } + + if( m_audioManager == nullptr || !m_audioManager->AreRoomsReady() ) + { + return; + } + + if( !EnsureSharedCubeGeometry() || !EnsureOutdoorRoomConfigured() ) + { + return; + } + + const AkUInt64 instanceID = GeometryInstanceIDForRoom( room ); + + // The room shape: the shared unit cube placed and scaled by the unit box transform. + AkTransform transform; + RH2LH::convertTransform( room.GetUnitBoxToWorld(), transform ); + + AkGeometryInstanceParams instanceParams; + instanceParams.PositionAndOrientation = transform; + instanceParams.Scale = RH2LH::extractScale( room.GetUnitBoxToWorld() ); + instanceParams.GeometrySetID = SHARED_CUBE_GEOMETRY_SET_ID; + instanceParams.UseForReflectionAndDiffraction = false; // containment only, keeps it out of the ray tracer + instanceParams.BypassPortalSubtraction = false; + instanceParams.IsSolid = false; + + AKRESULT result = AK::SpatialAudio::SetGeometryInstance( instanceID, instanceParams ); + if( result != AK_Success ) + { + CCP_LOGERR_CH( s_ch, "Failed to set geometry instance for room '%s' (%llu), AKRESULT: %d", + room.GetName().c_str(), room.GetRoomID(), result ); + return; + } + + AkRoomParams roomParams; + roomParams.Front = transform.OrientationFront(); + roomParams.Up = transform.OrientationTop(); + roomParams.ReverbAuxBus = room.GetReverbAuxBus().empty() + ? AK_INVALID_AUX_ID + : AK::SoundEngine::GetIDFromString( room.GetReverbAuxBus().c_str() ); + // Wwise documents these three as valid in [0, 1]. + roomParams.ReverbLevel = std::clamp( room.GetReverbLevel(), 0.0f, 1.0f ); + roomParams.TransmissionLoss = std::clamp( room.GetTransmissionLoss(), 0.0f, 1.0f ); + roomParams.RoomGameObj_AuxSendLevelToSelf = std::clamp( room.GetAuxSendLevelToSelf(), 0.0f, 1.0f ); + roomParams.RoomGameObj_KeepRegistered = room.GetKeepRegistered(); + roomParams.GeometryInstanceID = instanceID; + roomParams.RoomPriority = room.GetPriority(); + + const bool firstSend = !room.m_sentToWwise; + + // Calling SetRoom again with the same ID updates the room. + result = AK::SpatialAudio::SetRoom( room.GetRoomID(), roomParams, room.GetName().c_str() ); + if( result != AK_Success ) + { + CCP_LOGERR_CH( s_ch, "Failed to set room '%s' (%llu), AKRESULT: %d", + room.GetName().c_str(), room.GetRoomID(), result ); + if( firstSend ) + { + AK::SpatialAudio::RemoveGeometryInstance( instanceID ); + } + return; + } + + if( firstSend ) + { + // GetIDFromString hashes any name, so a mistyped bus is silent. Log what was resolved for the Profiler. + if( room.GetReverbAuxBus().empty() ) + { + CCP_LOG_CH( s_ch, "Room '%s' (%llu) sent to Wwise without a reverb aux bus.", room.GetName().c_str(), room.GetRoomID() ); + } + else + { + CCP_LOG_CH( s_ch, "Room '%s' (%llu) sent to Wwise, reverb aux bus '%s' -> %u.", + room.GetName().c_str(), room.GetRoomID(), room.GetReverbAuxBus().c_str(), roomParams.ReverbAuxBus ); + } + } + + room.m_sentToWwise = true; +} + +void AudRoomManager::RemoveLocked( AudRoom& room ) +{ + if( !room.m_sentToWwise ) + { + return; + } + + room.m_sentToWwise = false; + + if( AK::SoundEngine::IsInitialized() ) + { + // Remove the room before its geometry so nothing in Wwise references the instance. The shared cube stays. + AK::SpatialAudio::RemoveRoom( room.GetRoomID() ); + AK::SpatialAudio::RemoveGeometryInstance( GeometryInstanceIDForRoom( room ) ); + } +} + +void AudRoomManager::Push( AudRoom& room ) +{ + CcpAutoMutex lock( m_mutex ); + PushLocked( room ); +} + +void AudRoomManager::Remove( AudRoom& room ) +{ + CcpAutoMutex lock( m_mutex ); + RemoveLocked( room ); +} + +void AudRoomManager::RemoveAllFromWwise() +{ + CcpAutoMutex lock( m_mutex ); + for( auto& entry : m_rooms ) + { + RemoveLocked( *entry.second ); + } + + if( AK::SoundEngine::IsInitialized() ) + { + ReleaseSharedCubeGeometry(); + } + m_cubeSent = false; + // The outdoor room is Wwise's own, always-present room, so it is only parameterized here, never removed. + // Its parameters survive until the sound engine terminates, see ForgetWwiseState(). +} + +void AudRoomManager::ResendAll() +{ + CcpAutoMutex lock( m_mutex ); + for( auto& entry : m_rooms ) + { + PushLocked( *entry.second ); + } +} + +void AudRoomManager::ForgetWwiseState() +{ + CcpAutoMutex lock( m_mutex ); + for( auto& entry : m_rooms ) + { + entry.second->m_sentToWwise = false; + } + m_cubeSent = false; + m_outdoorConfigured = false; +} diff --git a/src/AudRoomManager.h b/src/AudRoomManager.h new file mode 100644 index 0000000..58d035c --- /dev/null +++ b/src/AudRoomManager.h @@ -0,0 +1,77 @@ +//////////////////////////////////////////////////////////// +// +// Creator: Phevos Rinis +// Creation Date: Sep 2026 +// Copyright (c) 2026 CCP Games +// + +#pragma once + +#include + +#include + +#include + +class AudManager; +class AudRoom; + +/** + * @brief Owns the Wwise side of every AudRoom: the shared room geometry, room registration and lifecycle. + * + * Every room is a scaled instance of one shared unit cube geometry set. The instance is marked as + * containment-only (not used for reflection or diffraction) so it costs nothing in the ray tracer; + * it gives Wwise the room's bounding box, shows the room in the Game Object 3D Viewer, and lets + * Wwise place game objects in the room. + * + * The manager mirrors the audio engine lifecycle: rooms are removed from Wwise when audio is disabled + * or Spatial Audio geometry is turned off, and re-sent when it comes back. + */ +class AudRoomManager +{ +public: + explicit AudRoomManager( AudManager* audioManager ); + ~AudRoomManager(); + + /// Called by AudRoom on construction and destruction. + void RegisterRoom( AudRoom* room ); + void UnregisterRoom( AudRoom* room ); + + /// Creates or updates the room in Wwise from the room's current state. + void Push( AudRoom& room ); + /// Removes the room from Wwise. The room stays known to the manager. + void Remove( AudRoom& room ); + + /// Removes every room from Wwise but keeps them known so they can be re-sent. For Disable() and the geometry switch turning off. + void RemoveAllFromWwise(); + /// Re-sends every enabled, placed room. For Enable() and the geometry switch turning on. + void ResendAll(); + /// Forgets what Wwise knows without talking to it. For after the sound engine has been terminated. + void ForgetWwiseState(); + + size_t GetRoomCount() const; + +private: + static constexpr AkUInt64 ROOM_SPATIAL_ID_TAG = 1ull << 62; + static constexpr AkUInt64 SHARED_CUBE_GEOMETRY_SET_ID = ROOM_SPATIAL_ID_TAG | 1ull; + + /// Geometry instance ID of a room, tagged so it can never collide with trinity's geometry IDs. + static AkUInt64 GeometryInstanceIDForRoom( const AudRoom& room ); + + /// Uploads the shared unit cube once. Caller holds m_mutex. + bool EnsureSharedCubeGeometry(); + /// Releases the shared unit cube. Only when all rooms leave Wwise, so toggling one room does not re-upload it. Caller holds m_mutex. + void ReleaseSharedCubeGeometry(); + /// Parameterizes Wwise's built-in outdoor room once: no transmission loss (as in the Wwise SDK samples) and no reverb. Caller holds m_mutex. + bool EnsureOutdoorRoomConfigured(); + /// Sends one room. Caller holds m_mutex. + void PushLocked( AudRoom& room ); + /// Removes one room. Caller holds m_mutex. + void RemoveLocked( AudRoom& room ); + + AudManager* m_audioManager; + std::unordered_map m_rooms; + bool m_cubeSent; + bool m_outdoorConfigured; + mutable CcpMutex m_mutex; +}; diff --git a/src/AudRoom_Blue.cpp b/src/AudRoom_Blue.cpp index ec6f17e..3ca0ba7 100644 --- a/src/AudRoom_Blue.cpp +++ b/src/AudRoom_Blue.cpp @@ -15,16 +15,18 @@ const Be::ClassInfo* AudRoom::ExposeToBlue() { EXPOSURE_BEGIN( AudRoom, "A Wwise Spatial Audio room. Trinity places it as a unit box through ITr2VolumeObject; the acoustic parameters live here." ) MAP_INTERFACE( ITr2VolumeObject ) + MAP_INTERFACE( INotify ) - MAP_ATTRIBUTE( "name", m_name, "Name of the room, shown in the Wwise profiler.", Be::READWRITE | Be::PERSIST ) - MAP_ATTRIBUTE( "reverbAuxBus", m_reverbAuxBus, "Name of the Wwise auxiliary bus carrying the reverb of this room. Empty for no reverb.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) - MAP_ATTRIBUTE( "reverbLevel", m_reverbLevel, "Send level [0, 1] from emitters inside the room to the reverb aux bus.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) - MAP_ATTRIBUTE( "transmissionLoss", m_transmissionLoss, "Transmission loss [0, 1] applied to sound passing through the walls of this room. 1 blocks everything.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) - MAP_ATTRIBUTE( "priority", m_priority, "When a game object is inside several rooms the room with the highest priority wins.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) - MAP_ATTRIBUTE( "auxSendLevelToSelf", m_auxSendLevelToSelf, "Send level [0, 1] of the room game object to its own reverb bus, for room tones posted on the room.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) - MAP_ATTRIBUTE( "keepRegistered", m_keepRegistered, "Keep the room game object registered in Wwise at all times, needed when posting events or RTPCs on the room.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST ) - MAP_ATTRIBUTE( "enabled", m_enabled, "Whether the room is acoustically active. The room is only sent to Wwise when this and the owning volume object are both enabled.", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE( "name", m_name, "Name of the room, shown in the Wwise profiler.", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "reverbAuxBus", m_reverbAuxBus, "Name of the Wwise auxiliary bus carrying the reverb of this room. Empty for no reverb.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "reverbLevel", m_reverbLevel, "Send level [0, 1] from emitters inside the room to the reverb aux bus.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "transmissionLoss", m_transmissionLoss, "Transmission loss [0, 1] applied to sound passing through the walls of this room. 1 blocks everything.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "priority", m_priority, "When a game object is inside several rooms the room with the highest priority wins.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "auxSendLevelToSelf", m_auxSendLevelToSelf, "Send level [0, 1] of the room game object to its own reverb bus, for room tones posted on the room.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "keepRegistered", m_keepRegistered, "Keep the room game object registered in Wwise at all times, needed when posting events or RTPCs on the room.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "enabled", m_enabled, "Whether the room is acoustically active. The room is only sent to Wwise when this and the owning volume object are both enabled.", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) MAP_ATTRIBUTE( "roomID", m_roomID, "Wwise room identifier.\n:jessica-hidden: True", Be::READ ) + MAP_ATTRIBUTE( "isRegistered", m_sentToWwise, "Whether the room currently exists in Wwise.\n:jessica-hidden: True", Be::READ ) EXPOSURE_END() } From e87365100dcaa773a25e8731990e0b7d140085b5 Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Thu, 8 Oct 2026 14:04:33 +0000 Subject: [PATCH 03/12] assign emitters and the listener to rooms --- src/AudGameObjResource.cpp | 4 + src/AudListener.cpp | 7 ++ src/AudManager.cpp | 2 + src/AudRoom.cpp | 38 ++++++++++ src/AudRoom.h | 10 ++- src/AudRoomManager.cpp | 152 ++++++++++++++++++++++++++++++++++++- src/AudRoomManager.h | 42 +++++++++- 7 files changed, 251 insertions(+), 4 deletions(-) diff --git a/src/AudGameObjResource.cpp b/src/AudGameObjResource.cpp index f742065..610edd1 100644 --- a/src/AudGameObjResource.cpp +++ b/src/AudGameObjResource.cpp @@ -143,6 +143,7 @@ void AudGameObjResource::UnregisterWwiseObject() return; } m_gameObjRegistered = false; + g_audioManager->GetRoomManager().ForgetGameObject( m_ID ); } } @@ -464,6 +465,9 @@ int AudGameObjResource::ApplyEffectivePlacement( const Vector3& front, const Vec RH2LH::convertEmitter( &soundPosLH, &tmp ); AK::SoundEngine::SetPosition( m_ID, soundPosLH ); + + // Explicit room containment, in the right-handed space rooms are placed in. + g_audioManager->GetRoomManager().UpdateGameObjectPosition( m_ID, position ); } return AK_Success; } diff --git a/src/AudListener.cpp b/src/AudListener.cpp index bc5a8d8..99ea108 100644 --- a/src/AudListener.cpp +++ b/src/AudListener.cpp @@ -22,6 +22,10 @@ AudListener::~AudListener() } AK::SoundEngine::RemoveDefaultListener( m_ID ); AK::SoundEngine::UnregisterGameObj( m_ID ); + if( g_audioManager != nullptr ) + { + g_audioManager->GetRoomManager().ForgetGameObject( m_ID ); + } } void AudListener::RegisterWwiseObject() @@ -66,6 +70,9 @@ int AudListener::SetPlacementFromParent( const Vector3& front, const Vector3& to RH2LH::convertListener( &soundPosLH, &tmp ); AK::SoundEngine::SetPosition( m_ID, soundPosLH ); + + // The listener's room decides what is heard as "inside"; assign it explicitly like every emitter. + g_audioManager->GetRoomManager().UpdateGameObjectPosition( m_ID, position ); } } return AK_Success; diff --git a/src/AudManager.cpp b/src/AudManager.cpp index 6b76300..c6c27f2 100644 --- a/src/AudManager.cpp +++ b/src/AudManager.cpp @@ -121,6 +121,8 @@ void AudManager::Process() } m_obstructionOcclusion->Update(); + // Re-assigns emitters and the listener to rooms when rooms changed this frame. + m_roomManager->Update(); // Process bank requests, events, positions, RTPC, etc. AK::SoundEngine::RenderAudio(); diff --git a/src/AudRoom.cpp b/src/AudRoom.cpp index a505741..ce1c651 100644 --- a/src/AudRoom.cpp +++ b/src/AudRoom.cpp @@ -11,6 +11,8 @@ #include "AudManager.h" #include "AudRoomManager.h" +#include + AudRoom::AudRoom( IRoot* lockobj ) : m_roomID( AllocateGameObjectID() ), m_reverbLevel( 1.0f ), @@ -21,6 +23,7 @@ AudRoom::AudRoom( IRoot* lockobj ) : m_enabled( true ), m_shapeEnabled( true ), m_unitBoxToWorld( IdentityMatrix() ), + m_volume( 0.0f ), m_shapeValid( false ), m_warnedDegenerate( false ), m_hasTransform( false ), @@ -76,6 +79,8 @@ void AudRoom::Sync() void AudRoom::SetTransform( const Matrix& unitBoxToWorld ) { m_unitBoxToWorld = unitBoxToWorld; + // The determinant of the affine part is the volume scale of the unit cube. + m_volume = std::fabs( Determinant( m_unitBoxToWorld ) ); m_hasTransform = true; // A box with a zero-length axis (e.g. a freshly placed, unscaled volume) must not become a 1 m cube in Wwise. @@ -94,6 +99,39 @@ void AudRoom::SetTransform( const Matrix& unitBoxToWorld ) Sync(); } +bool AudRoom::ContainsPoint( const Vector3& worldPosition ) const +{ + if( !m_hasTransform || !m_shapeValid ) + { + return false; + } + + // Row-vector convention: rows 1..3 are the box axes in world space (scaled), row 4 is the centre. + // A point is inside when its offset from the centre projects within half of each axis; no inverse needed. + const Matrix& m = m_unitBoxToWorld; + const Vector3 axes[3] = + { + Vector3( m._11, m._12, m._13 ), + Vector3( m._21, m._22, m._23 ), + Vector3( m._31, m._32, m._33 ), + }; + const Vector3 offset = worldPosition - Vector3( m._41, m._42, m._43 ); + + for( const Vector3& axis : axes ) + { + const float lengthSq = LengthSq( axis ); + if( lengthSq <= 1e-12f ) + { + return false; // degenerate box, contains nothing + } + if( std::fabs( Dot( offset, axis ) ) > 0.5f * lengthSq ) + { + return false; + } + } + return true; +} + void AudRoom::SetEnabled( bool enabled ) { if( m_shapeEnabled == enabled ) diff --git a/src/AudRoom.h b/src/AudRoom.h index c99cf9a..7a54624 100644 --- a/src/AudRoom.h +++ b/src/AudRoom.h @@ -21,7 +21,9 @@ * persisted attributes, the same split used between EveChildAudio and AudEmitter. * * The room is registered in Wwise with a containment-only geometry instance of a shared unit - * cube (see AudRoomManager), which Wwise uses to place game objects in the room. + * cube (see AudRoomManager). Containment is done twice: Wwise uses the cube for game objects we + * never assigned, and AudRoomManager tests every emitter and the listener against the box + * (ContainsPoint) and assigns them explicitly, so the game knows the room too. */ BLUE_CLASS( AudRoom ) : public ITr2VolumeObject, @@ -61,6 +63,11 @@ BLUE_CLASS( AudRoom ) : /// Whether the room currently exists in Wwise. bool IsRegistered() const { return m_sentToWwise; } + /// Game-side containment test, in right-handed world space (the same space SetTransform receives). + bool ContainsPoint( const Vector3& worldPosition ) const; + /// Volume of the box in cubic metres. Used to prefer the inner room when priorities tie, as Wwise does. + float GetVolume() const { return m_volume; } + private: friend class AudRoomManager; @@ -84,6 +91,7 @@ BLUE_CLASS( AudRoom ) : bool m_shapeEnabled; Matrix m_unitBoxToWorld; + float m_volume; bool m_shapeValid; bool m_warnedDegenerate; bool m_hasTransform; diff --git a/src/AudRoomManager.cpp b/src/AudRoomManager.cpp index bfc5ad5..418c6fd 100644 --- a/src/AudRoomManager.cpp +++ b/src/AudRoomManager.cpp @@ -44,10 +44,17 @@ namespace }; } +namespace +{ + const AkUInt64 OUTDOOR_ROOM_ID = static_cast( AK::SpatialAudio::kOutdoorRoomID ); +} + AudRoomManager::AudRoomManager( AudManager* audioManager ) : m_audioManager( audioManager ), m_cubeSent( false ), m_outdoorConfigured( false ), + m_assignmentsDirty( false ), + m_roomsInWwise( 0 ), m_mutex( "AudRoomManager", "m_mutex" ) { } @@ -80,6 +87,109 @@ size_t AudRoomManager::GetRoomCount() const return m_rooms.size(); } +size_t AudRoomManager::GetTrackedGameObjectCount() const +{ + CcpAutoMutex lock( m_mutex ); + return m_trackedObjects.size(); +} + +void AudRoomManager::UpdateGameObjectPosition( AkGameObjectID gameObjectID, const Vector3& position ) +{ + CcpAutoMutex lock( m_mutex ); + // Every emitter position send lands here; nothing to assign while no room is in Wwise. + if( m_roomsInWwise == 0 ) + { + return; + } + + TrackedGameObject& tracked = m_trackedObjects[gameObjectID]; + tracked.position = position; + AssignLocked( gameObjectID, tracked ); +} + +void AudRoomManager::ForgetGameObject( AkGameObjectID gameObjectID ) +{ + CcpAutoMutex lock( m_mutex ); + m_trackedObjects.erase( gameObjectID ); +} + +void AudRoomManager::Update() +{ + CcpAutoMutex lock( m_mutex ); + if( m_audioManager == nullptr || !m_audioManager->AreRoomsReady() ) + { + // Keep the flags; the next tick after rooms come back will catch up. + return; + } + + if( !m_assignmentsDirty ) + { + return; + } + + m_assignmentsDirty = false; + for( auto& entry : m_trackedObjects ) + { + AssignLocked( entry.first, entry.second ); + } +} + +AkUInt64 AudRoomManager::ResolveRoomLocked( const Vector3& position ) const +{ + const AudRoom* best = nullptr; + for( const auto& entry : m_rooms ) + { + const AudRoom* room = entry.second; + if( !room->m_sentToWwise || !room->ContainsPoint( position ) ) + { + continue; + } + + // Same rule as Wwise's own containment: highest priority, and the inner room on a tie. + const float priority = room->GetPriority(); + const float bestPriority = best != nullptr ? best->GetPriority() : 0.0f; + if( best == nullptr + || priority > bestPriority + || ( priority == bestPriority && room->GetVolume() < best->GetVolume() ) ) + { + best = room; + } + } + + return best != nullptr ? best->GetRoomID() : OUTDOOR_ROOM_ID; +} + +void AudRoomManager::AssignLocked( AkGameObjectID gameObjectID, TrackedGameObject& tracked ) +{ + if( m_audioManager == nullptr || !m_audioManager->AreRoomsReady() ) + { + return; + } + + const AkUInt64 roomID = ResolveRoomLocked( tracked.position ); + + if( tracked.assigned && tracked.roomID == roomID ) + { + return; + } + + if( !tracked.assigned && roomID == OUTDOOR_ROOM_ID ) + { + // Never placed in a room by us and not in one now: leave Wwise's own containment in charge. + return; + } + + const AKRESULT result = AK::SpatialAudio::SetGameObjectInRoom( gameObjectID, AkRoomID( roomID ) ); + if( result != AK_Success ) + { + CCP_LOGWARN_CH( s_ch, "Failed to set room %llu on game object %llu, AKRESULT: %d", roomID, gameObjectID, result ); + return; + } + + tracked.assigned = true; + tracked.roomID = roomID; +} + bool AudRoomManager::EnsureSharedCubeGeometry() { if( m_cubeSent ) @@ -224,6 +334,8 @@ void AudRoomManager::PushLocked( AudRoom& room ) if( firstSend ) { + ++m_roomsInWwise; + // GetIDFromString hashes any name, so a mistyped bus is silent. Log what was resolved for the Profiler. if( room.GetReverbAuxBus().empty() ) { @@ -237,6 +349,8 @@ void AudRoomManager::PushLocked( AudRoom& room ) } room.m_sentToWwise = true; + // The room appeared or moved; objects may have entered or left it. + m_assignmentsDirty = true; } void AudRoomManager::RemoveLocked( AudRoom& room ) @@ -247,13 +361,45 @@ void AudRoomManager::RemoveLocked( AudRoom& room ) } room.m_sentToWwise = false; + m_assignmentsDirty = true; + const bool lastRoom = --m_roomsInWwise == 0; + const bool soundEngineUp = AK::SoundEngine::IsInitialized(); - if( AK::SoundEngine::IsInitialized() ) + if( soundEngineUp ) { + // Objects we placed in this room go back to Wwise's own containment before the room disappears, + // so none is left on a stale override; the next Update() places them again if another room holds them. + for( auto& entry : m_trackedObjects ) + { + TrackedGameObject& tracked = entry.second; + if( tracked.assigned && tracked.roomID == room.GetRoomID() ) + { + AK::SpatialAudio::UnsetGameObjectInRoom( entry.first ); + tracked.assigned = false; + } + } + // Remove the room before its geometry so nothing in Wwise references the instance. The shared cube stays. AK::SpatialAudio::RemoveRoom( room.GetRoomID() ); AK::SpatialAudio::RemoveGeometryInstance( GeometryInstanceIDForRoom( room ) ); } + + if( lastRoom ) + { + // No room left: the remaining overrides (explicit outdoor) go back to Wwise too, and nothing is tracked + // until a room returns. Objects then fall back to Wwise's containment until their next position report. + if( soundEngineUp ) + { + for( const auto& entry : m_trackedObjects ) + { + if( entry.second.assigned ) + { + AK::SpatialAudio::UnsetGameObjectInRoom( entry.first ); + } + } + } + m_trackedObjects.clear(); + } } void AudRoomManager::Push( AudRoom& room ) @@ -301,6 +447,10 @@ void AudRoomManager::ForgetWwiseState() { entry.second->m_sentToWwise = false; } + m_roomsInWwise = 0; m_cubeSent = false; m_outdoorConfigured = false; + // Game objects are gone with the sound engine; they re-report their position when re-registered. + m_trackedObjects.clear(); + m_assignmentsDirty = false; } diff --git a/src/AudRoomManager.h b/src/AudRoomManager.h index 58d035c..23806f6 100644 --- a/src/AudRoomManager.h +++ b/src/AudRoomManager.h @@ -10,6 +10,7 @@ #include #include +#include "Vector3.h" #include @@ -17,12 +18,18 @@ class AudManager; class AudRoom; /** - * @brief Owns the Wwise side of every AudRoom: the shared room geometry, room registration and lifecycle. + * @brief Owns the Wwise side of every AudRoom: the shared room geometry, room registration, containment and lifecycle. * * Every room is a scaled instance of one shared unit cube geometry set. The instance is marked as * containment-only (not used for reflection or diffraction) so it costs nothing in the ray tracer; * it gives Wwise the room's bounding box, shows the room in the Game Object 3D Viewer, and lets - * Wwise place game objects in the room. + * Wwise place game objects we have not assigned ourselves. + * + * Containment is also done explicitly, the way the Wwise UE integration does it: every emitter and + * the listener report their position here, the manager tests them against the room boxes and calls + * AK::SpatialAudio::SetGameObjectInRoom when the room changes. Wwise never tells the game which room + * an object is in, so doing the test ourselves is what lets other systems (obstruction, portals later) + * know the room too. * * The manager mirrors the audio engine lifecycle: rooms are removed from Wwise when audio is disabled * or Spatial Audio geometry is turned off, and re-sent when it comes back. @@ -49,9 +56,30 @@ class AudRoomManager /// Forgets what Wwise knows without talking to it. For after the sound engine has been terminated. void ForgetWwiseState(); + /// Called whenever a registered game object (emitter or listener) sends a position to Wwise. + /// Position is right-handed world space, the same space rooms are placed in. Assigns the object + /// to the room containing it if that changed. + void UpdateGameObjectPosition( AkGameObjectID gameObjectID, const Vector3& position ); + /// Called when a game object is unregistered from Wwise; Wwise drops its room assignment with it. + void ForgetGameObject( AkGameObjectID gameObjectID ); + /// Once per audio tick: re-evaluates every tracked object when rooms were added, moved or removed. + void Update(); + size_t GetRoomCount() const; + size_t GetTrackedGameObjectCount() const; private: + struct TrackedGameObject + { + Vector3 position; + /// Room last sent with SetGameObjectInRoom. Only meaningful when assigned is true. + AkUInt64 roomID; + /// Whether our SetGameObjectInRoom override is in place for this object. While false, Wwise's own containment applies. + bool assigned; + + TrackedGameObject() : position( 0.0f, 0.0f, 0.0f ), roomID( 0 ), assigned( false ) {} + }; + static constexpr AkUInt64 ROOM_SPATIAL_ID_TAG = 1ull << 62; static constexpr AkUInt64 SHARED_CUBE_GEOMETRY_SET_ID = ROOM_SPATIAL_ID_TAG | 1ull; @@ -69,9 +97,19 @@ class AudRoomManager /// Removes one room. Caller holds m_mutex. void RemoveLocked( AudRoom& room ); + /// Room containing the position, or the outdoor room. Highest priority wins, then the smallest box. Caller holds m_mutex. + AkUInt64 ResolveRoomLocked( const Vector3& position ) const; + /// Sends SetGameObjectInRoom for one object if its room changed. Caller holds m_mutex. + void AssignLocked( AkGameObjectID gameObjectID, TrackedGameObject& tracked ); + AudManager* m_audioManager; std::unordered_map m_rooms; + std::unordered_map m_trackedObjects; bool m_cubeSent; bool m_outdoorConfigured; + /// Set when rooms changed in Wwise, so Update() re-evaluates every tracked object. + bool m_assignmentsDirty; + /// Rooms currently in Wwise. Position reports skip all work while there are none. + size_t m_roomsInWwise; mutable CcpMutex m_mutex; }; From efde046741ec50dcf20d155589762f37d688166f Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Thu, 8 Oct 2026 14:04:40 +0000 Subject: [PATCH 04/12] only resend a room when its parameters change --- src/AudRoom.h | 5 ++++ src/AudRoomManager.cpp | 56 +++++++++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/AudRoom.h b/src/AudRoom.h index 7a54624..5e9b5ae 100644 --- a/src/AudRoom.h +++ b/src/AudRoom.h @@ -10,6 +10,7 @@ #include #include +#include #include @@ -97,6 +98,10 @@ BLUE_CLASS( AudRoom ) : bool m_hasTransform; bool m_sentToWwise; bool m_registeredWithManager; + + /// What Wwise currently holds for this room, so unchanged parameters are not re-sent on every move. + AkRoomParams m_sentRoomParams; + std::string m_sentName; }; TYPEDEF_BLUECLASS( AudRoom ); diff --git a/src/AudRoomManager.cpp b/src/AudRoomManager.cpp index 418c6fd..cd0a424 100644 --- a/src/AudRoomManager.cpp +++ b/src/AudRoomManager.cpp @@ -13,6 +13,7 @@ #include "Utilities.h" #include +#include #include static CcpLogChannel_t s_ch = CCP_LOG_DEFINE_CHANNEL( "AudRoomManager" ); @@ -47,6 +48,28 @@ namespace namespace { const AkUInt64 OUTDOOR_ROOM_ID = static_cast( AK::SpatialAudio::kOutdoorRoomID ); + + /// Room orientation is rebuilt from a rotation matrix on every move; ignore float noise (same tolerance as trinity's EveVolumeObject). + bool OrientationsNearlyEqual( const AkVector& a, const AkVector& b ) + { + constexpr float epsilon = 1e-4f; + return std::fabs( a.X - b.X ) <= epsilon && std::fabs( a.Y - b.Y ) <= epsilon && std::fabs( a.Z - b.Z ) <= epsilon; + } + + /// Field-wise comparison of everything SetRoom consumes, so an unchanged room is not re-sent on every move. + bool RoomParamsEqual( const AkRoomParams& a, const AkRoomParams& b ) + { + return OrientationsNearlyEqual( a.Front, b.Front ) + && OrientationsNearlyEqual( a.Up, b.Up ) + && a.ReverbAuxBus == b.ReverbAuxBus + && a.ReverbLevel == b.ReverbLevel + && a.TransmissionLoss == b.TransmissionLoss + && a.RoomGameObj_AuxSendLevelToSelf == b.RoomGameObj_AuxSendLevelToSelf + && a.GeometryInstanceID == b.GeometryInstanceID + && a.RoomPriority == b.RoomPriority + && a.DistanceBehavior == b.DistanceBehavior + && a.RoomGameObj_KeepRegistered == b.RoomGameObj_KeepRegistered; + } } AudRoomManager::AudRoomManager( AudManager* audioManager ) : @@ -145,9 +168,10 @@ AkUInt64 AudRoomManager::ResolveRoomLocked( const Vector3& position ) const continue; } - // Same rule as Wwise's own containment: highest priority, and the inner room on a tie. - const float priority = room->GetPriority(); - const float bestPriority = best != nullptr ? best->GetPriority() : 0.0f; + // Same rule as Wwise's own containment: highest priority, and the inner room on a tie. The priority + // is the one Wwise holds, so both containments agree while an edited priority is not sent yet. + const float priority = room->m_sentRoomParams.RoomPriority; + const float bestPriority = best != nullptr ? best->m_sentRoomParams.RoomPriority : 0.0f; if( best == nullptr || priority > bestPriority || ( priority == bestPriority && room->GetVolume() < best->GetVolume() ) ) @@ -317,19 +341,29 @@ void AudRoomManager::PushLocked( AudRoom& room ) roomParams.GeometryInstanceID = instanceID; roomParams.RoomPriority = room.GetPriority(); + // Only Front/Up depend on the transform, so a pure move or resize only needs the geometry instance above. const bool firstSend = !room.m_sentToWwise; + const bool roomChanged = firstSend + || !RoomParamsEqual( roomParams, room.m_sentRoomParams ) + || room.GetName() != room.m_sentName; - // Calling SetRoom again with the same ID updates the room. - result = AK::SpatialAudio::SetRoom( room.GetRoomID(), roomParams, room.GetName().c_str() ); - if( result != AK_Success ) + if( roomChanged ) { - CCP_LOGERR_CH( s_ch, "Failed to set room '%s' (%llu), AKRESULT: %d", - room.GetName().c_str(), room.GetRoomID(), result ); - if( firstSend ) + // Calling SetRoom again with the same ID updates the room. + result = AK::SpatialAudio::SetRoom( room.GetRoomID(), roomParams, room.GetName().c_str() ); + if( result != AK_Success ) { - AK::SpatialAudio::RemoveGeometryInstance( instanceID ); + CCP_LOGERR_CH( s_ch, "Failed to set room '%s' (%llu), AKRESULT: %d", + room.GetName().c_str(), room.GetRoomID(), result ); + if( firstSend ) + { + AK::SpatialAudio::RemoveGeometryInstance( instanceID ); + } + return; } - return; + + room.m_sentRoomParams = roomParams; + room.m_sentName = room.GetName(); } if( firstSend ) From 93588458483bd163f11f1ae6e38aaa75eef340a3 Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Thu, 8 Oct 2026 14:04:40 +0000 Subject: [PATCH 05/12] make rooms and geometry safe to update from trinity worker threads --- src/AudGameObjResource.cpp | 7 ++++--- src/AudGeometry.cpp | 8 ++++++++ src/AudGeometry.h | 4 ++++ src/AudRoom.cpp | 28 +++++++++++++++++++++++----- src/AudRoom.h | 2 ++ src/AudRoomManager.cpp | 31 ++++++++++++++++++++++++++----- src/AudRoomManager.h | 11 +++++++++-- 7 files changed, 76 insertions(+), 15 deletions(-) diff --git a/src/AudGameObjResource.cpp b/src/AudGameObjResource.cpp index 610edd1..0ba2fc1 100644 --- a/src/AudGameObjResource.cpp +++ b/src/AudGameObjResource.cpp @@ -11,12 +11,13 @@ //----------------------------------------------------------------------------- // Helper function to distribute ID's. Shared with AudRoom so room IDs never -// collide with game object IDs. +// collide with game object IDs. Atomic because Blue objects can be created on +// loading threads. //----------------------------------------------------------------------------- AkGameObjectID AllocateGameObjectID() { - static AkGameObjectID s_currentID = START_GAME_OBJ_COUNT; - return s_currentID++; + static std::atomic s_currentID{ START_GAME_OBJ_COUNT }; + return s_currentID.fetch_add( 1, std::memory_order_relaxed ); } AudGameObjResource::Orientation::Orientation( const Vector3& front_, const Vector3& top_ ) : diff --git a/src/AudGeometry.cpp b/src/AudGeometry.cpp index e73f74c..d901e2f 100644 --- a/src/AudGeometry.cpp +++ b/src/AudGeometry.cpp @@ -45,6 +45,8 @@ AudGeometry::~AudGeometry() void AudGeometry::ClearAllGeometry() { + CcpAutoMutex lock( s_mutex ); + if( s_geometrySetRefCounts.empty() ) { return; @@ -89,6 +91,8 @@ void AudGeometry::SetGeometry( return; } + CcpAutoMutex lock( s_mutex ); + auto it = s_geometrySetRefCounts.find( geometrySetId ); if( it == s_geometrySetRefCounts.end() ) { @@ -143,6 +147,8 @@ void AudGeometry::SetGeometryTransform( return; } + CcpAutoMutex lock( s_mutex ); + if( s_geometrySetRefCounts.find( geometrySetId ) == s_geometrySetRefCounts.end() ) { return; @@ -161,6 +167,8 @@ void AudGeometry::RemoveGeometry( uint64_t geometrySetId, uint64_t instanceId ) { + CcpAutoMutex lock( s_mutex ); + auto it = s_geometrySetRefCounts.find( geometrySetId ); if( it == s_geometrySetRefCounts.end() ) { diff --git a/src/AudGeometry.h b/src/AudGeometry.h index c6159ff..d2c16ae 100644 --- a/src/AudGeometry.h +++ b/src/AudGeometry.h @@ -13,6 +13,8 @@ #include +#include + /** * @brief Registers meshes from Trinity as Spatial Audio geometry sets and manages their lifecycle. @@ -85,7 +87,9 @@ BLUE_CLASS(AudGeometry) : uint64_t geometrySetId, const Matrix& worldTransform ); /// Tracks how many active instances reference each geometry set. + /// Guarded by s_mutex: trinity registers meshes on its main thread but pushes transforms from worker threads. inline static std::unordered_map s_geometrySetRefCounts; + inline static CcpMutex s_mutex{ "AudGeometry", "s_mutex" }; }; TYPEDEF_BLUECLASS( AudGeometry ); diff --git a/src/AudRoom.cpp b/src/AudRoom.cpp index ce1c651..6ad73c6 100644 --- a/src/AudRoom.cpp +++ b/src/AudRoom.cpp @@ -77,6 +77,20 @@ void AudRoom::Sync() } void AudRoom::SetTransform( const Matrix& unitBoxToWorld ) +{ + if( EnsureRegistered() ) + { + // Emitter position reports, some from trinity worker threads, test against the box; the manager stores it under its lock. + g_audioManager->GetRoomManager().SetTransform( *this, unitBoxToWorld ); + } + else + { + // No audio manager yet: nothing reads the box, so store it for when the manager appears. + ApplyTransform( unitBoxToWorld ); + } +} + +void AudRoom::ApplyTransform( const Matrix& unitBoxToWorld ) { m_unitBoxToWorld = unitBoxToWorld; // The determinant of the affine part is the volume scale of the unit cube. @@ -94,9 +108,6 @@ void AudRoom::SetTransform( const Matrix& unitBoxToWorld ) CCP_LOGWARN( "Room '%s' (%llu) has a box with a zero-length axis; it is not sent to Wwise until it is scaled.", m_name.c_str(), m_roomID ); m_warnedDegenerate = true; } - - // Sends the room, or removes it when the new box is degenerate. - Sync(); } bool AudRoom::ContainsPoint( const Vector3& worldPosition ) const @@ -145,8 +156,15 @@ void AudRoom::SetEnabled( bool enabled ) void AudRoom::Remove() { - m_hasTransform = false; - Sync(); + if( EnsureRegistered() ) + { + // Clears the box under the manager's lock, then removes the room from Wwise. + g_audioManager->GetRoomManager().RemoveShape( *this ); + } + else + { + m_hasTransform = false; + } } bool AudRoom::OnModified( Be::Var* value ) diff --git a/src/AudRoom.h b/src/AudRoom.h index 5e9b5ae..252d8fd 100644 --- a/src/AudRoom.h +++ b/src/AudRoom.h @@ -76,6 +76,8 @@ BLUE_CLASS( AudRoom ) : bool EnsureRegistered(); /// Sends the current state to Wwise, or removes the room if it is disabled. void Sync(); + /// Stores the box. Called by AudRoomManager under its lock, because emitter position reports (also from trinity worker threads) read it for containment. + void ApplyTransform( const Matrix& unitBoxToWorld ); AkUInt64 m_roomID; diff --git a/src/AudRoomManager.cpp b/src/AudRoomManager.cpp index cd0a424..21155d0 100644 --- a/src/AudRoomManager.cpp +++ b/src/AudRoomManager.cpp @@ -118,9 +118,15 @@ size_t AudRoomManager::GetTrackedGameObjectCount() const void AudRoomManager::UpdateGameObjectPosition( AkGameObjectID gameObjectID, const Vector3& position ) { + // Every emitter position send lands here; skip the lock entirely while no room is in Wwise. + if( m_roomsInWwise.load( std::memory_order_acquire ) == 0 ) + { + return; + } + CcpAutoMutex lock( m_mutex ); - // Every emitter position send lands here; nothing to assign while no room is in Wwise. - if( m_roomsInWwise == 0 ) + // The last room may have left while this thread waited for the lock. + if( m_roomsInWwise.load( std::memory_order_relaxed ) == 0 ) { return; } @@ -368,7 +374,7 @@ void AudRoomManager::PushLocked( AudRoom& room ) if( firstSend ) { - ++m_roomsInWwise; + m_roomsInWwise.fetch_add( 1, std::memory_order_release ); // GetIDFromString hashes any name, so a mistyped bus is silent. Log what was resolved for the Profiler. if( room.GetReverbAuxBus().empty() ) @@ -396,7 +402,7 @@ void AudRoomManager::RemoveLocked( AudRoom& room ) room.m_sentToWwise = false; m_assignmentsDirty = true; - const bool lastRoom = --m_roomsInWwise == 0; + const bool lastRoom = m_roomsInWwise.fetch_sub( 1, std::memory_order_acq_rel ) == 1; const bool soundEngineUp = AK::SoundEngine::IsInitialized(); if( soundEngineUp ) @@ -448,6 +454,21 @@ void AudRoomManager::Remove( AudRoom& room ) RemoveLocked( room ); } +void AudRoomManager::SetTransform( AudRoom& room, const Matrix& unitBoxToWorld ) +{ + CcpAutoMutex lock( m_mutex ); + room.ApplyTransform( unitBoxToWorld ); + // Sends the room, or removes it when the new box is degenerate. + PushLocked( room ); +} + +void AudRoomManager::RemoveShape( AudRoom& room ) +{ + CcpAutoMutex lock( m_mutex ); + room.m_hasTransform = false; + RemoveLocked( room ); +} + void AudRoomManager::RemoveAllFromWwise() { CcpAutoMutex lock( m_mutex ); @@ -481,7 +502,7 @@ void AudRoomManager::ForgetWwiseState() { entry.second->m_sentToWwise = false; } - m_roomsInWwise = 0; + m_roomsInWwise.store( 0, std::memory_order_release ); m_cubeSent = false; m_outdoorConfigured = false; // Game objects are gone with the sound engine; they re-report their position when re-registered. diff --git a/src/AudRoomManager.h b/src/AudRoomManager.h index 23806f6..be9ee7e 100644 --- a/src/AudRoomManager.h +++ b/src/AudRoomManager.h @@ -10,8 +10,10 @@ #include #include +#include "Matrix.h" #include "Vector3.h" +#include #include class AudManager; @@ -48,6 +50,11 @@ class AudRoomManager void Push( AudRoom& room ); /// Removes the room from Wwise. The room stays known to the manager. void Remove( AudRoom& room ); + /// Stores the room's box under the lock and sends the room. The box is read for containment by emitter + /// position reports, which also arrive from trinity worker threads. + void SetTransform( AudRoom& room, const Matrix& unitBoxToWorld ); + /// Forgets the room's box under the lock and removes the room from Wwise. Pairs with ITr2VolumeObject::Remove. + void RemoveShape( AudRoom& room ); /// Removes every room from Wwise but keeps them known so they can be re-sent. For Disable() and the geometry switch turning off. void RemoveAllFromWwise(); @@ -109,7 +116,7 @@ class AudRoomManager bool m_outdoorConfigured; /// Set when rooms changed in Wwise, so Update() re-evaluates every tracked object. bool m_assignmentsDirty; - /// Rooms currently in Wwise. Position reports skip all work while there are none. - size_t m_roomsInWwise; + /// Rooms currently in Wwise. Written under m_mutex, read without it so position reports skip all work while there are none. + std::atomic m_roomsInWwise; mutable CcpMutex m_mutex; }; From 7580368f4c2c94dc826d937dc982a6f7ffdc679e Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Thu, 8 Oct 2026 14:04:57 +0000 Subject: [PATCH 06/12] fix a double delete in the AudManager destructor --- src/AudManager.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/AudManager.cpp b/src/AudManager.cpp index c6c27f2..00a476f 100644 --- a/src/AudManager.cpp +++ b/src/AudManager.cpp @@ -87,10 +87,7 @@ AudManager::AudManager( IRoot* lockobj ) : AudManager::~AudManager() { - // Clean up sound prioritization system - delete m_soundPrioritization; - delete m_spatialAudioSettings; - + // Disable() and Terminate() still use the prioritization and settings objects, so shut down first. if( GetState() == AudioState::Enabled ) { Disable(); @@ -101,8 +98,8 @@ AudManager::~AudManager() Terminate(); } - // Clean up sound prioritization system delete m_soundPrioritization; + delete m_spatialAudioSettings; } void AudManager::Process() From f6ba828995ff4e381fb27464236443b5cf2457db Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Thu, 8 Oct 2026 14:05:07 +0000 Subject: [PATCH 07/12] give rooms their own spatial audio switch --- src/AudListener.cpp | 6 +-- src/AudManager.cpp | 93 ++++++++++++++++++++++++++++++------ src/AudManager.h | 20 ++++++-- src/AudManager_Blue.cpp | 1 + src/AudRoomManager.h | 6 +-- src/SpatialAudioSettings.cpp | 4 ++ src/SpatialAudioSettings.h | 7 +++ 7 files changed, 111 insertions(+), 26 deletions(-) diff --git a/src/AudListener.cpp b/src/AudListener.cpp index 99ea108..aff2faa 100644 --- a/src/AudListener.cpp +++ b/src/AudListener.cpp @@ -16,7 +16,7 @@ AudListener::AudListener( IRoot* lockobj ) : AudGameObjResource( LISTENER_GAME_O AudListener::~AudListener() { - if( g_audioManager != nullptr && g_audioManager->GetSpatialAudioGeometryEnabled() ) + if( g_audioManager != nullptr && g_audioManager->IsSpatialAudioInitialized() ) { AK::SpatialAudio::UnregisterListener( m_ID ); } @@ -37,8 +37,8 @@ void AudListener::RegisterWwiseObject() AK::SoundEngine::RegisterGameObj(m_ID, m_name.c_str()); AK::SoundEngine::AddDefaultListener(m_ID); - // Register listener for occlusion/diffraction processing - if( g_audioManager != nullptr && g_audioManager->GetSpatialAudioGeometryEnabled() ) + // Register listener for Spatial Audio (geometry and rooms) + if( g_audioManager != nullptr && g_audioManager->IsSpatialAudioInitialized() ) { AK::SpatialAudio::RegisterListener( m_ID ); } diff --git a/src/AudManager.cpp b/src/AudManager.cpp index 00a476f..425c69b 100644 --- a/src/AudManager.cpp +++ b/src/AudManager.cpp @@ -68,7 +68,7 @@ AudManager::AudManager( IRoot* lockobj ) : m_asyncOpen( true ), m_log(), m_spatialAudioEnabled( true ), - m_spatialAudioGeometryInitialized( false ), + m_spatialAudioInitialized( false ), m_moniteredParametersMapMutex( "AudManager", "m_monitoredParametersMapMutex" ), m_soundBankMutex( "AudManager", "m_soundBankMutex" ), m_callbackGameObjectsMutex( "AudManager", "m_callbackGameObjectsMutex" ), @@ -150,7 +150,7 @@ AkBankID AudManager::ComputeWwiseHashForSoundBank( const std::wstring& soundBank bool AudManager::Init() { - m_spatialAudioGeometryInitialized = false; + m_spatialAudioInitialized = false; if( g_staticDataRepository == nullptr || !g_staticDataRepository->IsInitialized() ) { @@ -172,7 +172,7 @@ bool AudManager::Init() if( m_spatialAudioSettings->GetSpatialAudioGeometryEnabled() ) { - if( !InitSpatialAudioGeometry() ) + if( !InitSpatialAudio() ) { CCP_LOGERR( "Failed to initialize Spatial Audio Geometry" ); return false; @@ -224,7 +224,7 @@ void AudManager::Terminate() // The sound engine is gone, so is everything Spatial Audio knew about rooms. m_roomManager->ForgetWwiseState(); - m_spatialAudioGeometryInitialized = false; + m_spatialAudioInitialized = false; SetAudioState( AudioState::Uninitialized ); } @@ -448,9 +448,9 @@ bool AudManager::InitSound() return true; } -bool AudManager::InitSpatialAudioGeometry() +bool AudManager::InitSpatialAudio() { - if( m_spatialAudioGeometryInitialized ) + if( m_spatialAudioInitialized ) { return true; } @@ -460,12 +460,27 @@ bool AudManager::InitSpatialAudioGeometry() if( AK::SpatialAudio::Init( spatialSettings ) != AK_Success ) { - CCP_LOGERR( "Failed to initialize Wwise Spatial Audio for geometry processing" ); + CCP_LOGERR( "Failed to initialize Wwise Spatial Audio" ); return false; } - m_spatialAudioGeometryInitialized = true; - CCP_LOG_CH( s_ch, "Wwise Spatial Audio Geometry initialized" ); + m_spatialAudioInitialized = true; + CCP_LOG_CH( s_ch, "Wwise Spatial Audio initialized" ); + return true; +} + +bool AudManager::EnsureSpatialAudio() +{ + if( !InitSpatialAudio() ) + { + return false; + } + + // The listener may have been created before Spatial Audio was initialized. + if( AudListenerPtr listener = GetListener() ) + { + AK::SpatialAudio::RegisterListener( listener->GetID() ); + } return true; } @@ -503,11 +518,21 @@ bool AudManager::GetSpatialAudioGeometryEnabled() const return m_spatialAudioSettings->GetSpatialAudioGeometryEnabled(); } +bool AudManager::GetSpatialAudioRoomsEnabled() const +{ + return m_spatialAudioSettings->GetSpatialAudioRoomsEnabled(); +} + +bool AudManager::UsesSpatialAudio() const +{ + return GetSpatialAudioGeometryEnabled() || GetSpatialAudioRoomsEnabled(); +} + bool AudManager::AreRoomsReady() const { return GetState() == AudioState::Enabled - && m_spatialAudioGeometryInitialized - && GetSpatialAudioGeometryEnabled() + && m_spatialAudioInitialized + && GetSpatialAudioRoomsEnabled() && !g_shuttingDown; } @@ -528,25 +553,56 @@ void AudManager::SetSpatialAudioGeometryEnabled( bool enabled ) if( !enabled ) { m_spatialAudioSettings->SetSpatialAudioGeometryEnabled( false ); - // Rooms run on Spatial Audio geometry, so they leave Wwise with it. - m_roomManager->RemoveAllFromWwise(); AudGeometry::ClearAllGeometry(); CCP_LOG_CH( s_ch, "Spatial audio geometry disabled." ); } else { - if( !InitSpatialAudioGeometry() ) + if( !EnsureSpatialAudio() ) { CCP_LOGERR_CH( s_ch, "Spatial audio geometry failed to initialize." ); return; } m_spatialAudioSettings->SetSpatialAudioGeometryEnabled( true ); - m_roomManager->ResendAll(); CCP_LOG_CH( s_ch, "Spatial audio geometry enabled." ); } } +void AudManager::SetSpatialAudioRoomsEnabled( bool enabled ) +{ + if( GetSpatialAudioRoomsEnabled() == enabled ) + { + return; + } + + if( GetState() != AudioState::Enabled ) + { + // Enable() picks the setting up. + m_spatialAudioSettings->SetSpatialAudioRoomsEnabled( enabled ); + return; + } + + if( !enabled ) + { + m_spatialAudioSettings->SetSpatialAudioRoomsEnabled( false ); + m_roomManager->RemoveAllFromWwise(); + CCP_LOG_CH( s_ch, "Spatial audio rooms disabled." ); + } + else + { + if( !EnsureSpatialAudio() ) + { + CCP_LOGERR_CH( s_ch, "Spatial audio rooms failed to initialize." ); + return; + } + + m_spatialAudioSettings->SetSpatialAudioRoomsEnabled( true ); + m_roomManager->ResendAll(); + CCP_LOG_CH( s_ch, "Spatial audio rooms enabled." ); + } +} + const bool AudManager::SpatialAudioIsSupported() { return s_systemSupportsSpatialAudio; @@ -938,6 +994,13 @@ void AudManager::Enable( BankVector soundBanksToLoad ) } SetAudioState( AudioState::Enabled ); + + // Spatial Audio comes up before banks and game objects. Also covers a switch turned on while audio was disabled. + if( UsesSpatialAudio() && !EnsureSpatialAudio() ) + { + CCP_LOGERR_CH( s_ch, "Spatial audio failed to initialize; geometry and rooms are inactive." ); + } + LoadBank( L"Init.bnk" ); BankVector::iterator bankEnd = soundBanksToLoad.end(); diff --git a/src/AudManager.h b/src/AudManager.h index a9b7041..3a83ace 100644 --- a/src/AudManager.h +++ b/src/AudManager.h @@ -125,7 +125,15 @@ BLUE_CLASS( AudManager ) : bool GetSpatialAudioGeometryEnabled() const; // Enables or disables spatial audio geometry. void SetSpatialAudioGeometryEnabled( bool enabled ); - // True when Wwise Spatial Audio is initialized, geometry is on and audio is enabled, i.e. rooms may be sent. + // Returns whether Spatial Audio rooms are enabled. + bool GetSpatialAudioRoomsEnabled() const; + // Enables or disables Spatial Audio rooms. Independent of geometry. + void SetSpatialAudioRoomsEnabled( bool enabled ); + // True when either geometry or rooms are switched on, i.e. Wwise Spatial Audio is needed. + bool UsesSpatialAudio() const; + // True once Wwise Spatial Audio has been initialized in the current audio-engine lifetime. + bool IsSpatialAudioInitialized() const { return m_spatialAudioInitialized; } + // True when Wwise Spatial Audio is initialized, rooms are on and audio is enabled, i.e. rooms may be sent. bool AreRoomsReady() const; // Owns the Wwise side of every AudRoom. AudRoomManager& GetRoomManager() { return *m_roomManager; } @@ -230,8 +238,10 @@ BLUE_CLASS( AudManager ) : bool InitMusic(); // Initializes Wwise's sound engine. bool InitSound(); - // Initializes Wwise's Spatial Audio for geometry-based occlusion and diffraction. - bool InitSpatialAudioGeometry(); + // Initializes Wwise's Spatial Audio, used by both geometry and rooms. + bool InitSpatialAudio(); + // Initializes Spatial Audio if needed and registers the listener with it. + bool EnsureSpatialAudio(); // Tick handler void Process(); // Registers audio2 for the tick handler. @@ -261,8 +271,8 @@ BLUE_CLASS( AudManager ) : bool m_asyncOpen; // Signals whether Carbon Audio's spatial audio features are enabled. If the user currently doesn't have an active spatial audio endpoint then output will still be in stereo. bool m_spatialAudioEnabled; - // Tracks whether Wwise Spatial Audio geometry has been initialized in the current audio-engine lifetime. - bool m_spatialAudioGeometryInitialized; + // Tracks whether Wwise Spatial Audio has been initialized in the current audio-engine lifetime. + bool m_spatialAudioInitialized; mutable bool m_audioCullingEnabled; std::map m_soundBankInfoMap; diff --git a/src/AudManager_Blue.cpp b/src/AudManager_Blue.cpp index baae875..68453de 100644 --- a/src/AudManager_Blue.cpp +++ b/src/AudManager_Blue.cpp @@ -36,6 +36,7 @@ const Be::ClassInfo* AudManager::ExposeToBlue() // Spatial audio geometry settings MAP_PROPERTY( "spatialAudioGeometryEnabled", GetSpatialAudioGeometryEnabled, SetSpatialAudioGeometryEnabled, "Enable or disable spatial audio geometry.") + MAP_PROPERTY( "spatialAudioRoomsEnabled", GetSpatialAudioRoomsEnabled, SetSpatialAudioRoomsEnabled, "Enable or disable Spatial Audio rooms (reverb, room transmission loss, room tones). Independent of geometry.") MAP_PROPERTY( "movementThreshold", GetMovementThreshold, SetMovementThreshold, "Distance an emitter or listener must move to trigger a re-validation of reflections/diffraction.") MAP_PROPERTY( "numberOfPrimaryRays", GetNumberOfPrimaryRays, SetNumberOfPrimaryRays, "Number of primary rays used in the ray tracing engine. More rays = better quality but higher CPU.") MAP_PROPERTY( "maxReflectionOrder", GetMaxReflectionOrder, SetMaxReflectionOrder, "Maximum reflection order [1-4] - number of bounces in a reflection path.") diff --git a/src/AudRoomManager.h b/src/AudRoomManager.h index be9ee7e..7779d3c 100644 --- a/src/AudRoomManager.h +++ b/src/AudRoomManager.h @@ -34,7 +34,7 @@ class AudRoom; * know the room too. * * The manager mirrors the audio engine lifecycle: rooms are removed from Wwise when audio is disabled - * or Spatial Audio geometry is turned off, and re-sent when it comes back. + * or the rooms switch is turned off, and re-sent when it comes back. */ class AudRoomManager { @@ -56,9 +56,9 @@ class AudRoomManager /// Forgets the room's box under the lock and removes the room from Wwise. Pairs with ITr2VolumeObject::Remove. void RemoveShape( AudRoom& room ); - /// Removes every room from Wwise but keeps them known so they can be re-sent. For Disable() and the geometry switch turning off. + /// Removes every room from Wwise but keeps them known so they can be re-sent. For Disable() and the rooms switch turning off. void RemoveAllFromWwise(); - /// Re-sends every enabled, placed room. For Enable() and the geometry switch turning on. + /// Re-sends every enabled, placed room. For Enable() and the rooms switch turning on. void ResendAll(); /// Forgets what Wwise knows without talking to it. For after the sound engine has been terminated. void ForgetWwiseState(); diff --git a/src/SpatialAudioSettings.cpp b/src/SpatialAudioSettings.cpp index d1332ff..c8f4c87 100644 --- a/src/SpatialAudioSettings.cpp +++ b/src/SpatialAudioSettings.cpp @@ -4,6 +4,7 @@ SpatialAudioSettings::SpatialAudioSettings() : m_spatialAudioGeometryEnabled( false ) + , m_spatialAudioRoomsEnabled( false ) , m_movementThreshold( 100.0f ) , m_numberOfPrimaryRays( 35 ) , m_maxReflectionOrder( 0 ) @@ -24,6 +25,9 @@ SpatialAudioSettings::SpatialAudioSettings() bool SpatialAudioSettings::GetSpatialAudioGeometryEnabled() const { return m_spatialAudioGeometryEnabled; } void SpatialAudioSettings::SetSpatialAudioGeometryEnabled( bool value ) { m_spatialAudioGeometryEnabled = value; } +bool SpatialAudioSettings::GetSpatialAudioRoomsEnabled() const { return m_spatialAudioRoomsEnabled; } +void SpatialAudioSettings::SetSpatialAudioRoomsEnabled( bool value ) { m_spatialAudioRoomsEnabled = value; } + float SpatialAudioSettings::GetMovementThreshold() const { return m_movementThreshold; } void SpatialAudioSettings::SetMovementThreshold( float value ) { m_movementThreshold = value; } diff --git a/src/SpatialAudioSettings.h b/src/SpatialAudioSettings.h index ab0d367..e93e0e3 100644 --- a/src/SpatialAudioSettings.h +++ b/src/SpatialAudioSettings.h @@ -24,6 +24,12 @@ class SpatialAudioSettings bool GetSpatialAudioGeometryEnabled() const; void SetSpatialAudioGeometryEnabled( bool value ); + /** + * @brief Controls whether Spatial Audio rooms are sent to Wwise. Independent of geometry. + */ + bool GetSpatialAudioRoomsEnabled() const; + void SetSpatialAudioRoomsEnabled( bool value ); + /** * @brief Amount that an emitter or listener has to move to trigger a validation of reflections/diffraction. * @@ -194,6 +200,7 @@ class SpatialAudioSettings private: bool m_spatialAudioGeometryEnabled; + bool m_spatialAudioRoomsEnabled; float m_movementThreshold; int m_numberOfPrimaryRays; int m_maxReflectionOrder; From faed7ce7eb3c24c0f24199665a871ba457687243 Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Thu, 8 Oct 2026 14:05:13 +0000 Subject: [PATCH 08/12] add room tones to AudRoom --- src/AudManager.cpp | 5 ++ src/AudRoom.cpp | 4 +- src/AudRoom.h | 10 ++++ src/AudRoomManager.cpp | 118 ++++++++++++++++++++++++++++++++++++++++- src/AudRoomManager.h | 12 ++++- src/AudRoom_Blue.cpp | 3 +- 6 files changed, 147 insertions(+), 5 deletions(-) diff --git a/src/AudManager.cpp b/src/AudManager.cpp index 425c69b..ed0ecff 100644 --- a/src/AudManager.cpp +++ b/src/AudManager.cpp @@ -1129,6 +1129,11 @@ AudGameObjResource* AudManager::GetAudioEmitter( AkGameObjectID emitterID ) const std::wstring AudManager::GetEventName( AkGameObjectID emitterID, AkPlayingID playingID ) { AudGameObjResource* emitter = GetAudioEmitter( emitterID ); + if( emitter == nullptr ) + { + // Not one of ours, e.g. a room game object playing a room tone. + return L""; + } std::map playingEvents = emitter->GetPlayingEvents(); auto it = playingEvents.find( playingID ); if( it != playingEvents.end() ) diff --git a/src/AudRoom.cpp b/src/AudRoom.cpp index 6ad73c6..693ad4a 100644 --- a/src/AudRoom.cpp +++ b/src/AudRoom.cpp @@ -28,7 +28,9 @@ AudRoom::AudRoom( IRoot* lockobj ) : m_warnedDegenerate( false ), m_hasTransform( false ), m_sentToWwise( false ), - m_registeredWithManager( false ) + m_registeredWithManager( false ), + m_roomTonePlayingID( AK_INVALID_PLAYING_ID ), + m_roomTonePending( false ) { EnsureRegistered(); } diff --git a/src/AudRoom.h b/src/AudRoom.h index 252d8fd..24512e4 100644 --- a/src/AudRoom.h +++ b/src/AudRoom.h @@ -55,6 +55,8 @@ BLUE_CLASS( AudRoom ) : float GetPriority() const { return m_priority; } float GetAuxSendLevelToSelf() const { return m_auxSendLevelToSelf; } bool GetKeepRegistered() const { return m_keepRegistered; } + /// Wwise event played on the room game object while the room is in Wwise. Empty means no room tone. + const std::wstring& GetRoomToneEvent() const { return m_roomToneEvent; } /// Active only when both the authored attribute and the shape owner (trinity) say so. bool IsEnabled() const { return m_enabled && m_shapeEnabled; } bool HasTransform() const { return m_hasTransform; } @@ -88,6 +90,7 @@ BLUE_CLASS( AudRoom ) : float m_priority; float m_auxSendLevelToSelf; bool m_keepRegistered; + std::wstring m_roomToneEvent; /// Authored attribute: the acoustic side of "enabled". bool m_enabled; /// Set through ITr2VolumeObject::SetEnabled by whoever owns the shape. Starts enabled, never persisted. @@ -104,6 +107,13 @@ BLUE_CLASS( AudRoom ) : /// What Wwise currently holds for this room, so unchanged parameters are not re-sent on every move. AkRoomParams m_sentRoomParams; std::string m_sentName; + + /// Room tone state, owned by AudRoomManager under its lock. + /// The event last handled while the room is in Wwise (playing, waiting for SoundBanks, or failed); empty when none. + std::wstring m_postedRoomTone; + AkPlayingID m_roomTonePlayingID; + /// Waiting for the room tone's SoundBanks to load; AudRoomManager::Update() retries it. + bool m_roomTonePending; }; TYPEDEF_BLUECLASS( AudRoom ); diff --git a/src/AudRoomManager.cpp b/src/AudRoomManager.cpp index 21155d0..bb58d5a 100644 --- a/src/AudRoomManager.cpp +++ b/src/AudRoomManager.cpp @@ -10,6 +10,7 @@ #include "AudManager.h" #include "AudRoom.h" +#include "AudStaticDataRepository.h" #include "Utilities.h" #include @@ -78,6 +79,7 @@ AudRoomManager::AudRoomManager( AudManager* audioManager ) : m_outdoorConfigured( false ), m_assignmentsDirty( false ), m_roomsInWwise( 0 ), + m_pendingRoomTones( 0 ), m_mutex( "AudRoomManager", "m_mutex" ) { } @@ -151,6 +153,20 @@ void AudRoomManager::Update() return; } + if( m_pendingRoomTones > 0 ) + { + // Room tones whose SoundBanks were still loading when the room was sent. + for( auto& entry : m_rooms ) + { + AudRoom& room = *entry.second; + if( room.m_roomTonePending ) + { + const std::wstring eventName = room.m_postedRoomTone; + PostRoomToneLocked( room, eventName ); + } + } + } + if( !m_assignmentsDirty ) { return; @@ -333,6 +349,14 @@ void AudRoomManager::PushLocked( AudRoom& room ) return; } + // The room tone plays on the room game object. A changed tone stops the old one first, while SetRoom + // below has not yet released the game object; a plain move never touches the tone. + const std::wstring roomTone = StringUtils::trim( room.GetRoomToneEvent() ); + if( room.m_postedRoomTone != roomTone ) + { + StopRoomToneLocked( room ); + } + AkRoomParams roomParams; roomParams.Front = transform.OrientationFront(); roomParams.Up = transform.OrientationTop(); @@ -343,7 +367,8 @@ void AudRoomManager::PushLocked( AudRoom& room ) roomParams.ReverbLevel = std::clamp( room.GetReverbLevel(), 0.0f, 1.0f ); roomParams.TransmissionLoss = std::clamp( room.GetTransmissionLoss(), 0.0f, 1.0f ); roomParams.RoomGameObj_AuxSendLevelToSelf = std::clamp( room.GetAuxSendLevelToSelf(), 0.0f, 1.0f ); - roomParams.RoomGameObj_KeepRegistered = room.GetKeepRegistered(); + // Events can only be posted on the room game object while it stays registered. + roomParams.RoomGameObj_KeepRegistered = room.GetKeepRegistered() || !roomTone.empty(); roomParams.GeometryInstanceID = instanceID; roomParams.RoomPriority = room.GetPriority(); @@ -388,6 +413,11 @@ void AudRoomManager::PushLocked( AudRoom& room ) } } + if( !roomTone.empty() && room.m_postedRoomTone != roomTone ) + { + PostRoomToneLocked( room, roomTone ); + } + room.m_sentToWwise = true; // The room appeared or moved; objects may have entered or left it. m_assignmentsDirty = true; @@ -400,6 +430,9 @@ void AudRoomManager::RemoveLocked( AudRoom& room ) return; } + // The tone goes before the room releases its game object; it is posted again on the next send. + StopRoomToneLocked( room ); + room.m_sentToWwise = false; m_assignmentsDirty = true; const bool lastRoom = m_roomsInWwise.fetch_sub( 1, std::memory_order_acq_rel ) == 1; @@ -442,6 +475,81 @@ void AudRoomManager::RemoveLocked( AudRoom& room ) } } +void AudRoomManager::StopRoomToneLocked( AudRoom& room ) +{ + if( room.m_roomTonePlayingID != AK_INVALID_PLAYING_ID && AK::SoundEngine::IsInitialized() ) + { + AK::SoundEngine::StopPlayingID( room.m_roomTonePlayingID ); + } + room.m_roomTonePlayingID = AK_INVALID_PLAYING_ID; + + if( room.m_roomTonePending ) + { + room.m_roomTonePending = false; + --m_pendingRoomTones; + } + room.m_postedRoomTone.clear(); +} + +void AudRoomManager::PostRoomToneLocked( AudRoom& room, const std::wstring& eventName ) +{ + // From here on the tone counts as handled: playing, waiting for SoundBanks, or failed until its name changes. + room.m_postedRoomTone = eventName; + + if( g_staticDataRepository == nullptr ) + { + CCP_LOGERR_CH( s_ch, "Room '%s' (%llu): room tone %S cannot play without the static data repository.", + room.GetName().c_str(), room.GetRoomID(), eventName.c_str() ); + return; + } + + // Same checks as AudGameObjResource::PostEvent, without the emitter machinery. + const std::vector& soundBanks = g_staticDataRepository->SoundBanksRequiredForEvent( eventName ); + if( soundBanks.empty() ) + { + CCP_LOGERR_CH( s_ch, "Room '%s' (%llu): room tone %S is not in any SoundBank.", + room.GetName().c_str(), room.GetRoomID(), eventName.c_str() ); + return; + } + + for( const std::wstring& soundBank : soundBanks ) + { + const SoundBankStatus status = m_audioManager->GetSoundBankStatus( soundBank ); + if( status != SoundBankStatus::LOADED ) + { + if( !room.m_roomTonePending ) + { + room.m_roomTonePending = true; + ++m_pendingRoomTones; + if( status != SoundBankStatus::LOADING ) + { + CCP_LOGWARN_CH( s_ch, "Room '%s' (%llu): room tone %S waits for SoundBank %S, which is not loaded.", + room.GetName().c_str(), room.GetRoomID(), eventName.c_str(), soundBank.c_str() ); + } + } + return; + } + } + + if( room.m_roomTonePending ) + { + room.m_roomTonePending = false; + --m_pendingRoomTones; + } + + // The room ID doubles as the room game object, kept registered while a tone is set (see PushLocked). + const AkGameObjectID roomGameObjectID = AkRoomID( room.GetRoomID() ).AsGameObjectID(); + const AkUniqueID eventID = g_staticDataRepository->GetEventID( eventName ); + room.m_roomTonePlayingID = AK::SoundEngine::PostEvent( eventID, roomGameObjectID ); + m_audioManager->LogPostEvent( roomGameObjectID, room.m_roomTonePlayingID, eventID, eventName ); + + if( room.m_roomTonePlayingID == AK_INVALID_PLAYING_ID ) + { + CCP_LOGERR_CH( s_ch, "Room '%s' (%llu): room tone %S failed to play even though its SoundBanks are loaded.", + room.GetName().c_str(), room.GetRoomID(), eventName.c_str() ); + } +} + void AudRoomManager::Push( AudRoom& room ) { CcpAutoMutex lock( m_mutex ); @@ -500,8 +608,14 @@ void AudRoomManager::ForgetWwiseState() CcpAutoMutex lock( m_mutex ); for( auto& entry : m_rooms ) { - entry.second->m_sentToWwise = false; + AudRoom& room = *entry.second; + room.m_sentToWwise = false; + // Room tones died with the sound engine; nothing to stop. + room.m_roomTonePlayingID = AK_INVALID_PLAYING_ID; + room.m_roomTonePending = false; + room.m_postedRoomTone.clear(); } + m_pendingRoomTones = 0; m_roomsInWwise.store( 0, std::memory_order_release ); m_cubeSent = false; m_outdoorConfigured = false; diff --git a/src/AudRoomManager.h b/src/AudRoomManager.h index 7779d3c..41c6e81 100644 --- a/src/AudRoomManager.h +++ b/src/AudRoomManager.h @@ -34,7 +34,11 @@ class AudRoom; * know the room too. * * The manager mirrors the audio engine lifecycle: rooms are removed from Wwise when audio is disabled - * or the rooms switch is turned off, and re-sent when it comes back. + * or the rooms switch is turned off, and re-sent when it comes back. A room's tone plays on the room + * game object while the room is in Wwise and is posted again whenever the room is re-sent. + * + * Lock order: m_mutex may be held while AudManager's SoundBank and action-log locks are taken (room + * tones), never the other way round. */ class AudRoomManager { @@ -103,6 +107,10 @@ class AudRoomManager void PushLocked( AudRoom& room ); /// Removes one room. Caller holds m_mutex. void RemoveLocked( AudRoom& room ); + /// Stops the room tone and forgets it, so the next send posts it again. Caller holds m_mutex. + void StopRoomToneLocked( AudRoom& room ); + /// Plays the room tone on the room game object, or waits for its SoundBanks to load. Caller holds m_mutex. + void PostRoomToneLocked( AudRoom& room, const std::wstring& eventName ); /// Room containing the position, or the outdoor room. Highest priority wins, then the smallest box. Caller holds m_mutex. AkUInt64 ResolveRoomLocked( const Vector3& position ) const; @@ -118,5 +126,7 @@ class AudRoomManager bool m_assignmentsDirty; /// Rooms currently in Wwise. Written under m_mutex, read without it so position reports skip all work while there are none. std::atomic m_roomsInWwise; + /// Rooms whose tone waits for its SoundBanks; Update() retries them. + size_t m_pendingRoomTones; mutable CcpMutex m_mutex; }; diff --git a/src/AudRoom_Blue.cpp b/src/AudRoom_Blue.cpp index 3ca0ba7..132f8c8 100644 --- a/src/AudRoom_Blue.cpp +++ b/src/AudRoom_Blue.cpp @@ -23,7 +23,8 @@ const Be::ClassInfo* AudRoom::ExposeToBlue() MAP_ATTRIBUTE( "transmissionLoss", m_transmissionLoss, "Transmission loss [0, 1] applied to sound passing through the walls of this room. 1 blocks everything.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) MAP_ATTRIBUTE( "priority", m_priority, "When a game object is inside several rooms the room with the highest priority wins.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) MAP_ATTRIBUTE( "auxSendLevelToSelf", m_auxSendLevelToSelf, "Send level [0, 1] of the room game object to its own reverb bus, for room tones posted on the room.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) - MAP_ATTRIBUTE( "keepRegistered", m_keepRegistered, "Keep the room game object registered in Wwise at all times, needed when posting events or RTPCs on the room.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "keepRegistered", m_keepRegistered, "Keep the room game object registered in Wwise at all times, needed when posting events or RTPCs on the room. Always on while roomToneEvent is set.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "roomToneEvent", m_roomToneEvent, "Wwise event played on the room's own game object while the room is in Wwise, for ambience beds and room tones. Heard around the listener inside the room and through the walls from outside. Keeps the room game object registered.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) MAP_ATTRIBUTE( "enabled", m_enabled, "Whether the room is acoustically active. The room is only sent to Wwise when this and the owning volume object are both enabled.", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) MAP_ATTRIBUTE( "roomID", m_roomID, "Wwise room identifier.\n:jessica-hidden: True", Be::READ ) From 29b642a9ae185490da931a0e4a161cc19594a507 Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Fri, 9 Oct 2026 14:07:17 +0000 Subject: [PATCH 09/12] clean up comments in the rooms code --- src/AudGameObjResource.cpp | 6 +--- src/AudGeometry.h | 2 +- src/AudListener.cpp | 4 +-- src/AudManager.cpp | 9 ----- src/AudManager.h | 14 ++++---- src/AudManager_Blue.cpp | 2 +- src/AudRoom.cpp | 12 ++----- src/AudRoom.h | 40 +++++++-------------- src/AudRoomManager.cpp | 56 ++++++------------------------ src/AudRoomManager.h | 71 ++++++++++++++------------------------ src/AudRoom_Blue.cpp | 20 +++++------ src/Audio2.h | 3 +- src/SpatialAudioSettings.h | 2 +- 13 files changed, 73 insertions(+), 168 deletions(-) diff --git a/src/AudGameObjResource.cpp b/src/AudGameObjResource.cpp index 0ba2fc1..d8bd6c6 100644 --- a/src/AudGameObjResource.cpp +++ b/src/AudGameObjResource.cpp @@ -10,9 +10,7 @@ #include "SoundPrioritization.h" //----------------------------------------------------------------------------- -// Helper function to distribute ID's. Shared with AudRoom so room IDs never -// collide with game object IDs. Atomic because Blue objects can be created on -// loading threads. +// Helper function to distribute ID's, shared with AudRoom //----------------------------------------------------------------------------- AkGameObjectID AllocateGameObjectID() { @@ -466,8 +464,6 @@ int AudGameObjResource::ApplyEffectivePlacement( const Vector3& front, const Vec RH2LH::convertEmitter( &soundPosLH, &tmp ); AK::SoundEngine::SetPosition( m_ID, soundPosLH ); - - // Explicit room containment, in the right-handed space rooms are placed in. g_audioManager->GetRoomManager().UpdateGameObjectPosition( m_ID, position ); } return AK_Success; diff --git a/src/AudGeometry.h b/src/AudGeometry.h index d2c16ae..4614fcb 100644 --- a/src/AudGeometry.h +++ b/src/AudGeometry.h @@ -87,7 +87,7 @@ BLUE_CLASS(AudGeometry) : uint64_t geometrySetId, const Matrix& worldTransform ); /// Tracks how many active instances reference each geometry set. - /// Guarded by s_mutex: trinity registers meshes on its main thread but pushes transforms from worker threads. + /// Guarded by s_mutex, trinity sends transforms from worker threads. inline static std::unordered_map s_geometrySetRefCounts; inline static CcpMutex s_mutex{ "AudGeometry", "s_mutex" }; }; diff --git a/src/AudListener.cpp b/src/AudListener.cpp index aff2faa..d42d826 100644 --- a/src/AudListener.cpp +++ b/src/AudListener.cpp @@ -37,7 +37,7 @@ void AudListener::RegisterWwiseObject() AK::SoundEngine::RegisterGameObj(m_ID, m_name.c_str()); AK::SoundEngine::AddDefaultListener(m_ID); - // Register listener for Spatial Audio (geometry and rooms) + // Register listener for Spatial Audio if( g_audioManager != nullptr && g_audioManager->IsSpatialAudioInitialized() ) { AK::SpatialAudio::RegisterListener( m_ID ); @@ -70,8 +70,6 @@ int AudListener::SetPlacementFromParent( const Vector3& front, const Vector3& to RH2LH::convertListener( &soundPosLH, &tmp ); AK::SoundEngine::SetPosition( m_ID, soundPosLH ); - - // The listener's room decides what is heard as "inside"; assign it explicitly like every emitter. g_audioManager->GetRoomManager().UpdateGameObjectPosition( m_ID, position ); } } diff --git a/src/AudManager.cpp b/src/AudManager.cpp index ed0ecff..d9424fa 100644 --- a/src/AudManager.cpp +++ b/src/AudManager.cpp @@ -87,7 +87,6 @@ AudManager::AudManager( IRoot* lockobj ) : AudManager::~AudManager() { - // Disable() and Terminate() still use the prioritization and settings objects, so shut down first. if( GetState() == AudioState::Enabled ) { Disable(); @@ -118,7 +117,6 @@ void AudManager::Process() } m_obstructionOcclusion->Update(); - // Re-assigns emitters and the listener to rooms when rooms changed this frame. m_roomManager->Update(); // Process bank requests, events, positions, RTPC, etc. @@ -221,7 +219,6 @@ void AudManager::Terminate() // Terminate the Memory Manager AK::MemoryMgr::Term(); - // The sound engine is gone, so is everything Spatial Audio knew about rooms. m_roomManager->ForgetWwiseState(); m_spatialAudioInitialized = false; @@ -476,7 +473,6 @@ bool AudManager::EnsureSpatialAudio() return false; } - // The listener may have been created before Spatial Audio was initialized. if( AudListenerPtr listener = GetListener() ) { AK::SpatialAudio::RegisterListener( listener->GetID() ); @@ -578,7 +574,6 @@ void AudManager::SetSpatialAudioRoomsEnabled( bool enabled ) if( GetState() != AudioState::Enabled ) { - // Enable() picks the setting up. m_spatialAudioSettings->SetSpatialAudioRoomsEnabled( enabled ); return; } @@ -906,7 +901,6 @@ void AudManager::Disable() ClearBanks(); m_obstructionQuery = nullptr; m_obstructionOcclusion->Reset(); - // Rooms leave Wwise with audio and are re-sent by Enable(). m_roomManager->RemoveAllFromWwise(); AudGeometry::ClearAllGeometry(); #ifndef AK_OPTIMIZED @@ -995,7 +989,6 @@ void AudManager::Enable( BankVector soundBanksToLoad ) SetAudioState( AudioState::Enabled ); - // Spatial Audio comes up before banks and game objects. Also covers a switch turned on while audio was disabled. if( UsesSpatialAudio() && !EnsureSpatialAudio() ) { CCP_LOGERR_CH( s_ch, "Spatial audio failed to initialize; geometry and rooms are inactive." ); @@ -1015,7 +1008,6 @@ void AudManager::Enable( BankVector soundBanksToLoad ) obj->Wake(); } - // Rooms that were placed while audio was disabled, or removed by Disable(), go back into Wwise. m_roomManager->ResendAll(); BeOS->RegisterForTicks( this, (void*)"Audio::Tick" ); @@ -1131,7 +1123,6 @@ const std::wstring AudManager::GetEventName( AkGameObjectID emitterID, AkPlaying AudGameObjResource* emitter = GetAudioEmitter( emitterID ); if( emitter == nullptr ) { - // Not one of ours, e.g. a room game object playing a room tone. return L""; } std::map playingEvents = emitter->GetPlayingEvents(); diff --git a/src/AudManager.h b/src/AudManager.h index 3a83ace..5d68eb4 100644 --- a/src/AudManager.h +++ b/src/AudManager.h @@ -127,15 +127,15 @@ BLUE_CLASS( AudManager ) : void SetSpatialAudioGeometryEnabled( bool enabled ); // Returns whether Spatial Audio rooms are enabled. bool GetSpatialAudioRoomsEnabled() const; - // Enables or disables Spatial Audio rooms. Independent of geometry. + // Enables or disables Spatial Audio rooms. void SetSpatialAudioRoomsEnabled( bool enabled ); - // True when either geometry or rooms are switched on, i.e. Wwise Spatial Audio is needed. + // Returns whether geometry or rooms need Wwise Spatial Audio. bool UsesSpatialAudio() const; - // True once Wwise Spatial Audio has been initialized in the current audio-engine lifetime. + // Returns whether Wwise Spatial Audio is initialized. bool IsSpatialAudioInitialized() const { return m_spatialAudioInitialized; } - // True when Wwise Spatial Audio is initialized, rooms are on and audio is enabled, i.e. rooms may be sent. + // Returns whether rooms can be sent to Wwise. bool AreRoomsReady() const; - // Owns the Wwise side of every AudRoom. + // Returns the room manager. AudRoomManager& GetRoomManager() { return *m_roomManager; } // Set a single line-of-sight blockage ratio for an emitter [0.0, 1.0]. 0 = clear line of sight. bool SetEmitterLineOfSightBlockage( AkGameObjectID emitterID, float blockage ); @@ -238,7 +238,7 @@ BLUE_CLASS( AudManager ) : bool InitMusic(); // Initializes Wwise's sound engine. bool InitSound(); - // Initializes Wwise's Spatial Audio, used by both geometry and rooms. + // Initializes Wwise Spatial Audio. bool InitSpatialAudio(); // Initializes Spatial Audio if needed and registers the listener with it. bool EnsureSpatialAudio(); @@ -271,7 +271,7 @@ BLUE_CLASS( AudManager ) : bool m_asyncOpen; // Signals whether Carbon Audio's spatial audio features are enabled. If the user currently doesn't have an active spatial audio endpoint then output will still be in stereo. bool m_spatialAudioEnabled; - // Tracks whether Wwise Spatial Audio has been initialized in the current audio-engine lifetime. + // Whether Wwise Spatial Audio is initialized. bool m_spatialAudioInitialized; mutable bool m_audioCullingEnabled; diff --git a/src/AudManager_Blue.cpp b/src/AudManager_Blue.cpp index 68453de..8662628 100644 --- a/src/AudManager_Blue.cpp +++ b/src/AudManager_Blue.cpp @@ -36,7 +36,7 @@ const Be::ClassInfo* AudManager::ExposeToBlue() // Spatial audio geometry settings MAP_PROPERTY( "spatialAudioGeometryEnabled", GetSpatialAudioGeometryEnabled, SetSpatialAudioGeometryEnabled, "Enable or disable spatial audio geometry.") - MAP_PROPERTY( "spatialAudioRoomsEnabled", GetSpatialAudioRoomsEnabled, SetSpatialAudioRoomsEnabled, "Enable or disable Spatial Audio rooms (reverb, room transmission loss, room tones). Independent of geometry.") + MAP_PROPERTY( "spatialAudioRoomsEnabled", GetSpatialAudioRoomsEnabled, SetSpatialAudioRoomsEnabled, "Enable or disable Spatial Audio rooms.") MAP_PROPERTY( "movementThreshold", GetMovementThreshold, SetMovementThreshold, "Distance an emitter or listener must move to trigger a re-validation of reflections/diffraction.") MAP_PROPERTY( "numberOfPrimaryRays", GetNumberOfPrimaryRays, SetNumberOfPrimaryRays, "Number of primary rays used in the ray tracing engine. More rays = better quality but higher CPU.") MAP_PROPERTY( "maxReflectionOrder", GetMaxReflectionOrder, SetMaxReflectionOrder, "Maximum reflection order [1-4] - number of bounces in a reflection path.") diff --git a/src/AudRoom.cpp b/src/AudRoom.cpp index 693ad4a..9247486 100644 --- a/src/AudRoom.cpp +++ b/src/AudRoom.cpp @@ -82,12 +82,10 @@ void AudRoom::SetTransform( const Matrix& unitBoxToWorld ) { if( EnsureRegistered() ) { - // Emitter position reports, some from trinity worker threads, test against the box; the manager stores it under its lock. g_audioManager->GetRoomManager().SetTransform( *this, unitBoxToWorld ); } else { - // No audio manager yet: nothing reads the box, so store it for when the manager appears. ApplyTransform( unitBoxToWorld ); } } @@ -95,13 +93,11 @@ void AudRoom::SetTransform( const Matrix& unitBoxToWorld ) void AudRoom::ApplyTransform( const Matrix& unitBoxToWorld ) { m_unitBoxToWorld = unitBoxToWorld; - // The determinant of the affine part is the volume scale of the unit cube. m_volume = std::fabs( Determinant( m_unitBoxToWorld ) ); m_hasTransform = true; - // A box with a zero-length axis (e.g. a freshly placed, unscaled volume) must not become a 1 m cube in Wwise. const Matrix& m = m_unitBoxToWorld; - constexpr float minAxisLengthSq = 1e-6f; // 1 mm + constexpr float minAxisLengthSq = 1e-6f; m_shapeValid = LengthSq( Vector3( m._11, m._12, m._13 ) ) > minAxisLengthSq && LengthSq( Vector3( m._21, m._22, m._23 ) ) > minAxisLengthSq && LengthSq( Vector3( m._31, m._32, m._33 ) ) > minAxisLengthSq; @@ -119,8 +115,6 @@ bool AudRoom::ContainsPoint( const Vector3& worldPosition ) const return false; } - // Row-vector convention: rows 1..3 are the box axes in world space (scaled), row 4 is the centre. - // A point is inside when its offset from the centre projects within half of each axis; no inverse needed. const Matrix& m = m_unitBoxToWorld; const Vector3 axes[3] = { @@ -135,7 +129,7 @@ bool AudRoom::ContainsPoint( const Vector3& worldPosition ) const const float lengthSq = LengthSq( axis ); if( lengthSq <= 1e-12f ) { - return false; // degenerate box, contains nothing + return false; } if( std::fabs( Dot( offset, axis ) ) > 0.5f * lengthSq ) { @@ -160,7 +154,6 @@ void AudRoom::Remove() { if( EnsureRegistered() ) { - // Clears the box under the manager's lock, then removes the room from Wwise. g_audioManager->GetRoomManager().RemoveShape( *this ); } else @@ -171,7 +164,6 @@ void AudRoom::Remove() bool AudRoom::OnModified( Be::Var* value ) { - // Any authored attribute changed (e.g. edited in Graphite); re-send the room with the new parameters. Sync(); return true; } diff --git a/src/AudRoom.h b/src/AudRoom.h index 24512e4..1d99398 100644 --- a/src/AudRoom.h +++ b/src/AudRoom.h @@ -15,16 +15,7 @@ #include /** - * @brief A Wwise Spatial Audio room described by a unit box placed in the world. - * - * Implements ITr2VolumeObject so that trinity can place the box without knowing anything about - * acoustics. Everything acoustic (reverb aux bus, priority, transmission loss) lives here as - * persisted attributes, the same split used between EveChildAudio and AudEmitter. - * - * The room is registered in Wwise with a containment-only geometry instance of a shared unit - * cube (see AudRoomManager). Containment is done twice: Wwise uses the cube for game objects we - * never assigned, and AudRoomManager tests every emitter and the listener against the box - * (ContainsPoint) and assigns them explicitly, so the game knows the room too. + * @brief A Wwise Spatial Audio room, placed as a unit box through ITr2VolumeObject. */ BLUE_CLASS( AudRoom ) : public ITr2VolumeObject, @@ -44,41 +35,37 @@ BLUE_CLASS( AudRoom ) : // INotify bool OnModified( Be::Var* value ) override; - /// Room identifier. Shares the Wwise game object ID space, see AkRoomID. AkUInt64 GetRoomID() const { return m_roomID; } - /// Name shown in the Wwise profiler. const std::string& GetName() const { return m_name; } - /// Wwise aux bus name used for the room reverb. Empty means no reverb. const std::string& GetReverbAuxBus() const { return m_reverbAuxBus; } float GetReverbLevel() const { return m_reverbLevel; } float GetTransmissionLoss() const { return m_transmissionLoss; } float GetPriority() const { return m_priority; } float GetAuxSendLevelToSelf() const { return m_auxSendLevelToSelf; } bool GetKeepRegistered() const { return m_keepRegistered; } - /// Wwise event played on the room game object while the room is in Wwise. Empty means no room tone. const std::wstring& GetRoomToneEvent() const { return m_roomToneEvent; } - /// Active only when both the authored attribute and the shape owner (trinity) say so. + /// True when the room and its volume object are both enabled. bool IsEnabled() const { return m_enabled && m_shapeEnabled; } bool HasTransform() const { return m_hasTransform; } - /// False when the box has a zero-length axis; such a room contains nothing and is not sent to Wwise. + /// False when the box has a zero-length axis. bool HasUsableShape() const { return m_shapeValid; } const Matrix& GetUnitBoxToWorld() const { return m_unitBoxToWorld; } - /// Whether the room currently exists in Wwise. + /// Whether the room is in Wwise. bool IsRegistered() const { return m_sentToWwise; } - /// Game-side containment test, in right-handed world space (the same space SetTransform receives). + /// Returns whether the point is inside the room, in right-handed world space. bool ContainsPoint( const Vector3& worldPosition ) const; - /// Volume of the box in cubic metres. Used to prefer the inner room when priorities tie, as Wwise does. + /// Volume of the box, the smaller room wins when priorities are equal. float GetVolume() const { return m_volume; } private: friend class AudRoomManager; - /// Registers with the room manager if that has not happened yet (the manager may not exist at construction). + /// Registers with the room manager once it exists. bool EnsureRegistered(); - /// Sends the current state to Wwise, or removes the room if it is disabled. + /// Sends the room to Wwise, or removes it when disabled. void Sync(); - /// Stores the box. Called by AudRoomManager under its lock, because emitter position reports (also from trinity worker threads) read it for containment. + /// Stores the box. Called by AudRoomManager under its lock. void ApplyTransform( const Matrix& unitBoxToWorld ); AkUInt64 m_roomID; @@ -91,9 +78,8 @@ BLUE_CLASS( AudRoom ) : float m_auxSendLevelToSelf; bool m_keepRegistered; std::wstring m_roomToneEvent; - /// Authored attribute: the acoustic side of "enabled". bool m_enabled; - /// Set through ITr2VolumeObject::SetEnabled by whoever owns the shape. Starts enabled, never persisted. + /// Set through ITr2VolumeObject::SetEnabled. bool m_shapeEnabled; Matrix m_unitBoxToWorld; @@ -104,15 +90,13 @@ BLUE_CLASS( AudRoom ) : bool m_sentToWwise; bool m_registeredWithManager; - /// What Wwise currently holds for this room, so unchanged parameters are not re-sent on every move. + /// Last parameters sent to Wwise. AkRoomParams m_sentRoomParams; std::string m_sentName; - /// Room tone state, owned by AudRoomManager under its lock. - /// The event last handled while the room is in Wwise (playing, waiting for SoundBanks, or failed); empty when none. + /// Room tone state, owned by AudRoomManager. std::wstring m_postedRoomTone; AkPlayingID m_roomTonePlayingID; - /// Waiting for the room tone's SoundBanks to load; AudRoomManager::Update() retries it. bool m_roomTonePending; }; diff --git a/src/AudRoomManager.cpp b/src/AudRoomManager.cpp index bb58d5a..5615e39 100644 --- a/src/AudRoomManager.cpp +++ b/src/AudRoomManager.cpp @@ -21,8 +21,7 @@ static CcpLogChannel_t s_ch = CCP_LOG_DEFINE_CHANNEL( "AudRoomManager" ); namespace { - // Unit cube [-0.5, 0.5]^3. Defined directly in Wwise space: the cube is symmetric under the - // right- to left-handed Z flip, and Wwise triangles are double-sided so winding does not matter. + /// Unit cube [-0.5, 0.5]^3, the same in right- and left-handed space. const AkVertex CUBE_VERTICES[8] = { AkVertex( -0.5f, -0.5f, -0.5f ), @@ -37,12 +36,12 @@ namespace const AkTriangle CUBE_TRIANGLES[12] = { - AkTriangle( 0, 1, 2, 0 ), AkTriangle( 0, 2, 3, 0 ), // -Z - AkTriangle( 4, 6, 5, 0 ), AkTriangle( 4, 7, 6, 0 ), // +Z - AkTriangle( 0, 5, 1, 0 ), AkTriangle( 0, 4, 5, 0 ), // -Y - AkTriangle( 3, 2, 6, 0 ), AkTriangle( 3, 6, 7, 0 ), // +Y - AkTriangle( 0, 3, 7, 0 ), AkTriangle( 0, 7, 4, 0 ), // -X - AkTriangle( 1, 5, 6, 0 ), AkTriangle( 1, 6, 2, 0 ), // +X + AkTriangle( 0, 1, 2, 0 ), AkTriangle( 0, 2, 3, 0 ), + AkTriangle( 4, 6, 5, 0 ), AkTriangle( 4, 7, 6, 0 ), + AkTriangle( 0, 5, 1, 0 ), AkTriangle( 0, 4, 5, 0 ), + AkTriangle( 3, 2, 6, 0 ), AkTriangle( 3, 6, 7, 0 ), + AkTriangle( 0, 3, 7, 0 ), AkTriangle( 0, 7, 4, 0 ), + AkTriangle( 1, 5, 6, 0 ), AkTriangle( 1, 6, 2, 0 ), }; } @@ -50,14 +49,14 @@ namespace { const AkUInt64 OUTDOOR_ROOM_ID = static_cast( AK::SpatialAudio::kOutdoorRoomID ); - /// Room orientation is rebuilt from a rotation matrix on every move; ignore float noise (same tolerance as trinity's EveVolumeObject). + /// Compares orientations with a small tolerance for float errors. bool OrientationsNearlyEqual( const AkVector& a, const AkVector& b ) { constexpr float epsilon = 1e-4f; return std::fabs( a.X - b.X ) <= epsilon && std::fabs( a.Y - b.Y ) <= epsilon && std::fabs( a.Z - b.Z ) <= epsilon; } - /// Field-wise comparison of everything SetRoom consumes, so an unchanged room is not re-sent on every move. + /// Compares every parameter SetRoom uses. bool RoomParamsEqual( const AkRoomParams& a, const AkRoomParams& b ) { return OrientationsNearlyEqual( a.Front, b.Front ) @@ -120,14 +119,12 @@ size_t AudRoomManager::GetTrackedGameObjectCount() const void AudRoomManager::UpdateGameObjectPosition( AkGameObjectID gameObjectID, const Vector3& position ) { - // Every emitter position send lands here; skip the lock entirely while no room is in Wwise. if( m_roomsInWwise.load( std::memory_order_acquire ) == 0 ) { return; } CcpAutoMutex lock( m_mutex ); - // The last room may have left while this thread waited for the lock. if( m_roomsInWwise.load( std::memory_order_relaxed ) == 0 ) { return; @@ -149,13 +146,11 @@ void AudRoomManager::Update() CcpAutoMutex lock( m_mutex ); if( m_audioManager == nullptr || !m_audioManager->AreRoomsReady() ) { - // Keep the flags; the next tick after rooms come back will catch up. return; } if( m_pendingRoomTones > 0 ) { - // Room tones whose SoundBanks were still loading when the room was sent. for( auto& entry : m_rooms ) { AudRoom& room = *entry.second; @@ -190,8 +185,6 @@ AkUInt64 AudRoomManager::ResolveRoomLocked( const Vector3& position ) const continue; } - // Same rule as Wwise's own containment: highest priority, and the inner room on a tie. The priority - // is the one Wwise holds, so both containments agree while an edited priority is not sent yet. const float priority = room->m_sentRoomParams.RoomPriority; const float bestPriority = best != nullptr ? best->m_sentRoomParams.RoomPriority : 0.0f; if( best == nullptr @@ -221,7 +214,6 @@ void AudRoomManager::AssignLocked( AkGameObjectID gameObjectID, TrackedGameObjec if( !tracked.assigned && roomID == OUTDOOR_ROOM_ID ) { - // Never placed in a room by us and not in one now: leave Wwise's own containment in charge. return; } @@ -243,9 +235,6 @@ bool AudRoomManager::EnsureSharedCubeGeometry() return true; } - // One placeholder surface for the whole cube until rooms get acoustic materials. Containment-only geometry - // is not ray traced, so the room's own TransmissionLoss applies to direct paths; Wwise still uses room - // geometry surfaces for the transmission of reverb and room tones through the walls. AkAcousticSurface surface; surface.strName = "AudRoomCube"; surface.textureID = AK_INVALID_UNIQUE_ID; @@ -290,8 +279,6 @@ bool AudRoomManager::EnsureOutdoorRoomConfigured() return true; } - // Wwise applies room transmission loss from both the emitter's and the listener's room. Give the - // outdoor room (space, station exteriors) none, so an interior's own TransmissionLoss is what is heard. AkRoomParams params; params.TransmissionLoss = 0.0f; params.ReverbAuxBus = AK_INVALID_AUX_ID; @@ -329,7 +316,6 @@ void AudRoomManager::PushLocked( AudRoom& room ) const AkUInt64 instanceID = GeometryInstanceIDForRoom( room ); - // The room shape: the shared unit cube placed and scaled by the unit box transform. AkTransform transform; RH2LH::convertTransform( room.GetUnitBoxToWorld(), transform ); @@ -337,7 +323,7 @@ void AudRoomManager::PushLocked( AudRoom& room ) instanceParams.PositionAndOrientation = transform; instanceParams.Scale = RH2LH::extractScale( room.GetUnitBoxToWorld() ); instanceParams.GeometrySetID = SHARED_CUBE_GEOMETRY_SET_ID; - instanceParams.UseForReflectionAndDiffraction = false; // containment only, keeps it out of the ray tracer + instanceParams.UseForReflectionAndDiffraction = false; instanceParams.BypassPortalSubtraction = false; instanceParams.IsSolid = false; @@ -349,8 +335,6 @@ void AudRoomManager::PushLocked( AudRoom& room ) return; } - // The room tone plays on the room game object. A changed tone stops the old one first, while SetRoom - // below has not yet released the game object; a plain move never touches the tone. const std::wstring roomTone = StringUtils::trim( room.GetRoomToneEvent() ); if( room.m_postedRoomTone != roomTone ) { @@ -363,16 +347,13 @@ void AudRoomManager::PushLocked( AudRoom& room ) roomParams.ReverbAuxBus = room.GetReverbAuxBus().empty() ? AK_INVALID_AUX_ID : AK::SoundEngine::GetIDFromString( room.GetReverbAuxBus().c_str() ); - // Wwise documents these three as valid in [0, 1]. roomParams.ReverbLevel = std::clamp( room.GetReverbLevel(), 0.0f, 1.0f ); roomParams.TransmissionLoss = std::clamp( room.GetTransmissionLoss(), 0.0f, 1.0f ); roomParams.RoomGameObj_AuxSendLevelToSelf = std::clamp( room.GetAuxSendLevelToSelf(), 0.0f, 1.0f ); - // Events can only be posted on the room game object while it stays registered. roomParams.RoomGameObj_KeepRegistered = room.GetKeepRegistered() || !roomTone.empty(); roomParams.GeometryInstanceID = instanceID; roomParams.RoomPriority = room.GetPriority(); - // Only Front/Up depend on the transform, so a pure move or resize only needs the geometry instance above. const bool firstSend = !room.m_sentToWwise; const bool roomChanged = firstSend || !RoomParamsEqual( roomParams, room.m_sentRoomParams ) @@ -380,7 +361,6 @@ void AudRoomManager::PushLocked( AudRoom& room ) if( roomChanged ) { - // Calling SetRoom again with the same ID updates the room. result = AK::SpatialAudio::SetRoom( room.GetRoomID(), roomParams, room.GetName().c_str() ); if( result != AK_Success ) { @@ -401,7 +381,6 @@ void AudRoomManager::PushLocked( AudRoom& room ) { m_roomsInWwise.fetch_add( 1, std::memory_order_release ); - // GetIDFromString hashes any name, so a mistyped bus is silent. Log what was resolved for the Profiler. if( room.GetReverbAuxBus().empty() ) { CCP_LOG_CH( s_ch, "Room '%s' (%llu) sent to Wwise without a reverb aux bus.", room.GetName().c_str(), room.GetRoomID() ); @@ -419,7 +398,6 @@ void AudRoomManager::PushLocked( AudRoom& room ) } room.m_sentToWwise = true; - // The room appeared or moved; objects may have entered or left it. m_assignmentsDirty = true; } @@ -430,7 +408,6 @@ void AudRoomManager::RemoveLocked( AudRoom& room ) return; } - // The tone goes before the room releases its game object; it is posted again on the next send. StopRoomToneLocked( room ); room.m_sentToWwise = false; @@ -440,8 +417,6 @@ void AudRoomManager::RemoveLocked( AudRoom& room ) if( soundEngineUp ) { - // Objects we placed in this room go back to Wwise's own containment before the room disappears, - // so none is left on a stale override; the next Update() places them again if another room holds them. for( auto& entry : m_trackedObjects ) { TrackedGameObject& tracked = entry.second; @@ -452,15 +427,12 @@ void AudRoomManager::RemoveLocked( AudRoom& room ) } } - // Remove the room before its geometry so nothing in Wwise references the instance. The shared cube stays. AK::SpatialAudio::RemoveRoom( room.GetRoomID() ); AK::SpatialAudio::RemoveGeometryInstance( GeometryInstanceIDForRoom( room ) ); } if( lastRoom ) { - // No room left: the remaining overrides (explicit outdoor) go back to Wwise too, and nothing is tracked - // until a room returns. Objects then fall back to Wwise's containment until their next position report. if( soundEngineUp ) { for( const auto& entry : m_trackedObjects ) @@ -493,7 +465,6 @@ void AudRoomManager::StopRoomToneLocked( AudRoom& room ) void AudRoomManager::PostRoomToneLocked( AudRoom& room, const std::wstring& eventName ) { - // From here on the tone counts as handled: playing, waiting for SoundBanks, or failed until its name changes. room.m_postedRoomTone = eventName; if( g_staticDataRepository == nullptr ) @@ -503,7 +474,6 @@ void AudRoomManager::PostRoomToneLocked( AudRoom& room, const std::wstring& even return; } - // Same checks as AudGameObjResource::PostEvent, without the emitter machinery. const std::vector& soundBanks = g_staticDataRepository->SoundBanksRequiredForEvent( eventName ); if( soundBanks.empty() ) { @@ -537,7 +507,6 @@ void AudRoomManager::PostRoomToneLocked( AudRoom& room, const std::wstring& even --m_pendingRoomTones; } - // The room ID doubles as the room game object, kept registered while a tone is set (see PushLocked). const AkGameObjectID roomGameObjectID = AkRoomID( room.GetRoomID() ).AsGameObjectID(); const AkUniqueID eventID = g_staticDataRepository->GetEventID( eventName ); room.m_roomTonePlayingID = AK::SoundEngine::PostEvent( eventID, roomGameObjectID ); @@ -566,7 +535,6 @@ void AudRoomManager::SetTransform( AudRoom& room, const Matrix& unitBoxToWorld ) { CcpAutoMutex lock( m_mutex ); room.ApplyTransform( unitBoxToWorld ); - // Sends the room, or removes it when the new box is degenerate. PushLocked( room ); } @@ -590,8 +558,6 @@ void AudRoomManager::RemoveAllFromWwise() ReleaseSharedCubeGeometry(); } m_cubeSent = false; - // The outdoor room is Wwise's own, always-present room, so it is only parameterized here, never removed. - // Its parameters survive until the sound engine terminates, see ForgetWwiseState(). } void AudRoomManager::ResendAll() @@ -610,7 +576,6 @@ void AudRoomManager::ForgetWwiseState() { AudRoom& room = *entry.second; room.m_sentToWwise = false; - // Room tones died with the sound engine; nothing to stop. room.m_roomTonePlayingID = AK_INVALID_PLAYING_ID; room.m_roomTonePending = false; room.m_postedRoomTone.clear(); @@ -619,7 +584,6 @@ void AudRoomManager::ForgetWwiseState() m_roomsInWwise.store( 0, std::memory_order_release ); m_cubeSent = false; m_outdoorConfigured = false; - // Game objects are gone with the sound engine; they re-report their position when re-registered. m_trackedObjects.clear(); m_assignmentsDirty = false; } diff --git a/src/AudRoomManager.h b/src/AudRoomManager.h index 41c6e81..27002f5 100644 --- a/src/AudRoomManager.h +++ b/src/AudRoomManager.h @@ -20,25 +20,9 @@ class AudManager; class AudRoom; /** - * @brief Owns the Wwise side of every AudRoom: the shared room geometry, room registration, containment and lifecycle. + * @brief Sends every AudRoom to Wwise and assigns game objects to rooms. * - * Every room is a scaled instance of one shared unit cube geometry set. The instance is marked as - * containment-only (not used for reflection or diffraction) so it costs nothing in the ray tracer; - * it gives Wwise the room's bounding box, shows the room in the Game Object 3D Viewer, and lets - * Wwise place game objects we have not assigned ourselves. - * - * Containment is also done explicitly, the way the Wwise UE integration does it: every emitter and - * the listener report their position here, the manager tests them against the room boxes and calls - * AK::SpatialAudio::SetGameObjectInRoom when the room changes. Wwise never tells the game which room - * an object is in, so doing the test ourselves is what lets other systems (obstruction, portals later) - * know the room too. - * - * The manager mirrors the audio engine lifecycle: rooms are removed from Wwise when audio is disabled - * or the rooms switch is turned off, and re-sent when it comes back. A room's tone plays on the room - * game object while the room is in Wwise and is posted again whenever the room is re-sent. - * - * Lock order: m_mutex may be held while AudManager's SoundBank and action-log locks are taken (room - * tones), never the other way round. + * Lock order: m_mutex can be held while taking AudManager's SoundBank and action-log locks, never the other way round. */ class AudRoomManager { @@ -50,30 +34,27 @@ class AudRoomManager void RegisterRoom( AudRoom* room ); void UnregisterRoom( AudRoom* room ); - /// Creates or updates the room in Wwise from the room's current state. + /// Creates or updates the room in Wwise. void Push( AudRoom& room ); - /// Removes the room from Wwise. The room stays known to the manager. + /// Removes the room from Wwise. void Remove( AudRoom& room ); - /// Stores the room's box under the lock and sends the room. The box is read for containment by emitter - /// position reports, which also arrive from trinity worker threads. + /// Stores the room's box under the lock and sends the room. void SetTransform( AudRoom& room, const Matrix& unitBoxToWorld ); - /// Forgets the room's box under the lock and removes the room from Wwise. Pairs with ITr2VolumeObject::Remove. + /// Clears the room's box and removes the room from Wwise. void RemoveShape( AudRoom& room ); - /// Removes every room from Wwise but keeps them known so they can be re-sent. For Disable() and the rooms switch turning off. + /// Removes every room from Wwise, for Disable() and the rooms switch. void RemoveAllFromWwise(); - /// Re-sends every enabled, placed room. For Enable() and the rooms switch turning on. + /// Sends every enabled room to Wwise, for Enable() and the rooms switch. void ResendAll(); - /// Forgets what Wwise knows without talking to it. For after the sound engine has been terminated. + /// Resets the Wwise state after the sound engine is terminated. void ForgetWwiseState(); - /// Called whenever a registered game object (emitter or listener) sends a position to Wwise. - /// Position is right-handed world space, the same space rooms are placed in. Assigns the object - /// to the room containing it if that changed. + /// Assigns the game object to the room it is in. Position is in right-handed world space. void UpdateGameObjectPosition( AkGameObjectID gameObjectID, const Vector3& position ); - /// Called when a game object is unregistered from Wwise; Wwise drops its room assignment with it. + /// Removes a game object that was unregistered from Wwise. void ForgetGameObject( AkGameObjectID gameObjectID ); - /// Once per audio tick: re-evaluates every tracked object when rooms were added, moved or removed. + /// Assigns game objects again when rooms changed and retries room tones waiting for SoundBanks. void Update(); size_t GetRoomCount() const; @@ -83,9 +64,9 @@ class AudRoomManager struct TrackedGameObject { Vector3 position; - /// Room last sent with SetGameObjectInRoom. Only meaningful when assigned is true. + /// Room last sent with SetGameObjectInRoom. AkUInt64 roomID; - /// Whether our SetGameObjectInRoom override is in place for this object. While false, Wwise's own containment applies. + /// Whether SetGameObjectInRoom was called for this object. bool assigned; TrackedGameObject() : position( 0.0f, 0.0f, 0.0f ), roomID( 0 ), assigned( false ) {} @@ -94,27 +75,27 @@ class AudRoomManager static constexpr AkUInt64 ROOM_SPATIAL_ID_TAG = 1ull << 62; static constexpr AkUInt64 SHARED_CUBE_GEOMETRY_SET_ID = ROOM_SPATIAL_ID_TAG | 1ull; - /// Geometry instance ID of a room, tagged so it can never collide with trinity's geometry IDs. + /// Geometry instance ID of the room, tagged so it can't collide with trinity's geometry IDs. static AkUInt64 GeometryInstanceIDForRoom( const AudRoom& room ); - /// Uploads the shared unit cube once. Caller holds m_mutex. + /// Sends the shared unit cube once. Caller holds m_mutex. bool EnsureSharedCubeGeometry(); - /// Releases the shared unit cube. Only when all rooms leave Wwise, so toggling one room does not re-upload it. Caller holds m_mutex. + /// Removes the shared unit cube. Caller holds m_mutex. void ReleaseSharedCubeGeometry(); - /// Parameterizes Wwise's built-in outdoor room once: no transmission loss (as in the Wwise SDK samples) and no reverb. Caller holds m_mutex. + /// Sets the outdoor room to no transmission loss and no reverb. Caller holds m_mutex. bool EnsureOutdoorRoomConfigured(); /// Sends one room. Caller holds m_mutex. void PushLocked( AudRoom& room ); - /// Removes one room. Caller holds m_mutex. + /// Removes one room with its room tone and game object assignments. Caller holds m_mutex. void RemoveLocked( AudRoom& room ); - /// Stops the room tone and forgets it, so the next send posts it again. Caller holds m_mutex. + /// Stops the room tone. Caller holds m_mutex. void StopRoomToneLocked( AudRoom& room ); - /// Plays the room tone on the room game object, or waits for its SoundBanks to load. Caller holds m_mutex. + /// Plays the room tone, or waits for its SoundBanks. Caller holds m_mutex. void PostRoomToneLocked( AudRoom& room, const std::wstring& eventName ); - /// Room containing the position, or the outdoor room. Highest priority wins, then the smallest box. Caller holds m_mutex. + /// Returns the room containing the position, or the outdoor room. Caller holds m_mutex. AkUInt64 ResolveRoomLocked( const Vector3& position ) const; - /// Sends SetGameObjectInRoom for one object if its room changed. Caller holds m_mutex. + /// Calls SetGameObjectInRoom when the object's room changed. Caller holds m_mutex. void AssignLocked( AkGameObjectID gameObjectID, TrackedGameObject& tracked ); AudManager* m_audioManager; @@ -122,11 +103,11 @@ class AudRoomManager std::unordered_map m_trackedObjects; bool m_cubeSent; bool m_outdoorConfigured; - /// Set when rooms changed in Wwise, so Update() re-evaluates every tracked object. + /// Set when rooms changed, so Update() assigns game objects again. bool m_assignmentsDirty; - /// Rooms currently in Wwise. Written under m_mutex, read without it so position reports skip all work while there are none. + /// Number of rooms in Wwise, read without the lock. std::atomic m_roomsInWwise; - /// Rooms whose tone waits for its SoundBanks; Update() retries them. + /// Room tones waiting for SoundBanks. size_t m_pendingRoomTones; mutable CcpMutex m_mutex; }; diff --git a/src/AudRoom_Blue.cpp b/src/AudRoom_Blue.cpp index 132f8c8..2907562 100644 --- a/src/AudRoom_Blue.cpp +++ b/src/AudRoom_Blue.cpp @@ -13,21 +13,21 @@ BLUE_DEFINE_INTERFACE( ITr2VolumeObject ); const Be::ClassInfo* AudRoom::ExposeToBlue() { - EXPOSURE_BEGIN( AudRoom, "A Wwise Spatial Audio room. Trinity places it as a unit box through ITr2VolumeObject; the acoustic parameters live here." ) + EXPOSURE_BEGIN( AudRoom, "A Wwise Spatial Audio room, placed as a unit box through ITr2VolumeObject" ) MAP_INTERFACE( ITr2VolumeObject ) MAP_INTERFACE( INotify ) MAP_ATTRIBUTE( "name", m_name, "Name of the room, shown in the Wwise profiler.", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) - MAP_ATTRIBUTE( "reverbAuxBus", m_reverbAuxBus, "Name of the Wwise auxiliary bus carrying the reverb of this room. Empty for no reverb.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) - MAP_ATTRIBUTE( "reverbLevel", m_reverbLevel, "Send level [0, 1] from emitters inside the room to the reverb aux bus.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) - MAP_ATTRIBUTE( "transmissionLoss", m_transmissionLoss, "Transmission loss [0, 1] applied to sound passing through the walls of this room. 1 blocks everything.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) - MAP_ATTRIBUTE( "priority", m_priority, "When a game object is inside several rooms the room with the highest priority wins.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) - MAP_ATTRIBUTE( "auxSendLevelToSelf", m_auxSendLevelToSelf, "Send level [0, 1] of the room game object to its own reverb bus, for room tones posted on the room.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) - MAP_ATTRIBUTE( "keepRegistered", m_keepRegistered, "Keep the room game object registered in Wwise at all times, needed when posting events or RTPCs on the room. Always on while roomToneEvent is set.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) - MAP_ATTRIBUTE( "roomToneEvent", m_roomToneEvent, "Wwise event played on the room's own game object while the room is in Wwise, for ambience beds and room tones. Heard around the listener inside the room and through the walls from outside. Keeps the room game object registered.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) - MAP_ATTRIBUTE( "enabled", m_enabled, "Whether the room is acoustically active. The room is only sent to Wwise when this and the owning volume object are both enabled.", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "reverbAuxBus", m_reverbAuxBus, "Wwise auxiliary bus with the reverb of this room. Empty for no reverb.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "reverbLevel", m_reverbLevel, "Send level [0, 1] from emitters in the room to the reverb bus.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "transmissionLoss", m_transmissionLoss, "Transmission loss [0, 1] through the walls of this room. 1 blocks everything.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "priority", m_priority, "The room with the highest priority wins when rooms overlap.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "auxSendLevelToSelf", m_auxSendLevelToSelf, "Send level [0, 1] from the room tone to the reverb bus.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "keepRegistered", m_keepRegistered, "Keep the room game object registered in Wwise, needed for events and RTPCs on the room. Always on with a room tone.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "roomToneEvent", m_roomToneEvent, "Wwise event played on the room while it is in Wwise, e.g. an ambience bed.\n:jessica-group: Acoustics", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) + MAP_ATTRIBUTE( "enabled", m_enabled, "Whether the room is active. It is only sent to Wwise when its volume object is enabled too.", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) MAP_ATTRIBUTE( "roomID", m_roomID, "Wwise room identifier.\n:jessica-hidden: True", Be::READ ) - MAP_ATTRIBUTE( "isRegistered", m_sentToWwise, "Whether the room currently exists in Wwise.\n:jessica-hidden: True", Be::READ ) + MAP_ATTRIBUTE( "isRegistered", m_sentToWwise, "Whether the room is in Wwise.\n:jessica-hidden: True", Be::READ ) EXPOSURE_END() } diff --git a/src/Audio2.h b/src/Audio2.h index 27f49f9..6e20432 100644 --- a/src/Audio2.h +++ b/src/Audio2.h @@ -26,8 +26,7 @@ inline bool IsReservedGameObjectID( AkGameObjectID id ) return id < START_GAME_OBJ_COUNT; } -// Hands out the next free Wwise game object ID. Also used for Spatial Audio rooms, -// which share the game object ID space (see AkRoomID). +// Returns the next game object ID, also used for rooms. AkGameObjectID AllocateGameObjectID(); // Makes sure objects are initialized far away so you don't hear them when they spawn. diff --git a/src/SpatialAudioSettings.h b/src/SpatialAudioSettings.h index e93e0e3..8e33bf6 100644 --- a/src/SpatialAudioSettings.h +++ b/src/SpatialAudioSettings.h @@ -25,7 +25,7 @@ class SpatialAudioSettings void SetSpatialAudioGeometryEnabled( bool value ); /** - * @brief Controls whether Spatial Audio rooms are sent to Wwise. Independent of geometry. + * @brief Controls whether Spatial Audio rooms are sent to Wwise. */ bool GetSpatialAudioRoomsEnabled() const; void SetSpatialAudioRoomsEnabled( bool value ); From 62dff158f25087d08fb91818000ad3b05eab7c96 Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Fri, 9 Oct 2026 14:12:31 +0000 Subject: [PATCH 10/12] restore the AudRoom doxygen and the AudManager destructor comment --- src/AudManager.cpp | 1 + src/AudRoom.h | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/AudManager.cpp b/src/AudManager.cpp index d9424fa..51b4a38 100644 --- a/src/AudManager.cpp +++ b/src/AudManager.cpp @@ -97,6 +97,7 @@ AudManager::~AudManager() Terminate(); } + // Clean up sound prioritization system delete m_soundPrioritization; delete m_spatialAudioSettings; } diff --git a/src/AudRoom.h b/src/AudRoom.h index 1d99398..7cd89f1 100644 --- a/src/AudRoom.h +++ b/src/AudRoom.h @@ -35,14 +35,18 @@ BLUE_CLASS( AudRoom ) : // INotify bool OnModified( Be::Var* value ) override; + /// Room ID, from the same ID space as game objects. AkUInt64 GetRoomID() const { return m_roomID; } + /// Name shown in the Wwise profiler. const std::string& GetName() const { return m_name; } + /// Reverb aux bus name. Empty means no reverb. const std::string& GetReverbAuxBus() const { return m_reverbAuxBus; } float GetReverbLevel() const { return m_reverbLevel; } float GetTransmissionLoss() const { return m_transmissionLoss; } float GetPriority() const { return m_priority; } float GetAuxSendLevelToSelf() const { return m_auxSendLevelToSelf; } bool GetKeepRegistered() const { return m_keepRegistered; } + /// Room tone event. Empty means no room tone. const std::wstring& GetRoomToneEvent() const { return m_roomToneEvent; } /// True when the room and its volume object are both enabled. bool IsEnabled() const { return m_enabled && m_shapeEnabled; } @@ -78,6 +82,7 @@ BLUE_CLASS( AudRoom ) : float m_auxSendLevelToSelf; bool m_keepRegistered; std::wstring m_roomToneEvent; + /// Enabled attribute of the room. bool m_enabled; /// Set through ITr2VolumeObject::SetEnabled. bool m_shapeEnabled; @@ -95,8 +100,10 @@ BLUE_CLASS( AudRoom ) : std::string m_sentName; /// Room tone state, owned by AudRoomManager. + /// Room tone event last posted. Empty when none. std::wstring m_postedRoomTone; AkPlayingID m_roomTonePlayingID; + /// Room tone waiting for its SoundBanks. bool m_roomTonePending; }; From d17048f279fbd3b46fec717c7da58330c0e9045b Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Mon, 12 Oct 2026 10:02:50 +0000 Subject: [PATCH 11/12] rename ITr2VolumeObject to ITr2BoxVolume --- src/AudRoom.h | 10 +++++----- src/AudRoom_Blue.cpp | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/AudRoom.h b/src/AudRoom.h index 7cd89f1..f6bface 100644 --- a/src/AudRoom.h +++ b/src/AudRoom.h @@ -7,7 +7,7 @@ #pragma once -#include +#include #include #include @@ -15,10 +15,10 @@ #include /** - * @brief A Wwise Spatial Audio room, placed as a unit box through ITr2VolumeObject. + * @brief A Wwise Spatial Audio room, placed as a unit box through ITr2BoxVolume. */ BLUE_CLASS( AudRoom ) : - public ITr2VolumeObject, + public ITr2BoxVolume, public INotify { public: @@ -27,7 +27,7 @@ BLUE_CLASS( AudRoom ) : EXPOSE_TO_BLUE(); - // ITr2VolumeObject + // ITr2BoxVolume void SetTransform( const Matrix& unitBoxToWorld ) override; void SetEnabled( bool enabled ) override; void Remove() override; @@ -84,7 +84,7 @@ BLUE_CLASS( AudRoom ) : std::wstring m_roomToneEvent; /// Enabled attribute of the room. bool m_enabled; - /// Set through ITr2VolumeObject::SetEnabled. + /// Set through ITr2BoxVolume::SetEnabled. bool m_shapeEnabled; Matrix m_unitBoxToWorld; diff --git a/src/AudRoom_Blue.cpp b/src/AudRoom_Blue.cpp index 2907562..ec9b414 100644 --- a/src/AudRoom_Blue.cpp +++ b/src/AudRoom_Blue.cpp @@ -9,12 +9,12 @@ #include "AudRoom.h" BLUE_DEFINE( AudRoom ); -BLUE_DEFINE_INTERFACE( ITr2VolumeObject ); +BLUE_DEFINE_INTERFACE( ITr2BoxVolume ); const Be::ClassInfo* AudRoom::ExposeToBlue() { - EXPOSURE_BEGIN( AudRoom, "A Wwise Spatial Audio room, placed as a unit box through ITr2VolumeObject" ) - MAP_INTERFACE( ITr2VolumeObject ) + EXPOSURE_BEGIN( AudRoom, "A Wwise Spatial Audio room, placed as a unit box through ITr2BoxVolume" ) + MAP_INTERFACE( ITr2BoxVolume ) MAP_INTERFACE( INotify ) MAP_ATTRIBUTE( "name", m_name, "Name of the room, shown in the Wwise profiler.", Be::READWRITE | Be::PERSIST | Be::NOTIFY ) From 0198483140ebf53c19d0fd992d2473acadd0a89e Mon Sep 17 00:00:00 2001 From: Phevos Rinis Date: Tue, 13 Oct 2026 11:10:22 +0000 Subject: [PATCH 12/12] address review comments --- src/AudListener.cpp | 15 +++++++++------ src/AudListener.h | 3 +++ src/AudManager.cpp | 15 +++------------ vcpkg.json | 2 +- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/AudListener.cpp b/src/AudListener.cpp index d42d826..e58cdad 100644 --- a/src/AudListener.cpp +++ b/src/AudListener.cpp @@ -37,13 +37,8 @@ void AudListener::RegisterWwiseObject() AK::SoundEngine::RegisterGameObj(m_ID, m_name.c_str()); AK::SoundEngine::AddDefaultListener(m_ID); - // Register listener for Spatial Audio - if( g_audioManager != nullptr && g_audioManager->IsSpatialAudioInitialized() ) - { - AK::SpatialAudio::RegisterListener( m_ID ); - } - m_gameObjRegistered = true; + RegisterWithSpatialAudio(); } } else @@ -53,6 +48,14 @@ void AudListener::RegisterWwiseObject() } } +void AudListener::RegisterWithSpatialAudio() +{ + if( m_gameObjRegistered && g_audioManager != nullptr && g_audioManager->IsSpatialAudioInitialized() ) + { + AK::SpatialAudio::RegisterListener( m_ID ); + } +} + int AudListener::SetPlacementFromParent( const Vector3& front, const Vector3& top, const Vector3& position ) { if( g_audioManager != nullptr && g_audioManager->GetState() != AudioState::Uninitialized ) diff --git a/src/AudListener.h b/src/AudListener.h index 27a422e..83d0d98 100644 --- a/src/AudListener.h +++ b/src/AudListener.h @@ -25,6 +25,9 @@ BLUE_CLASS( AudListener ) : public AudGameObjResource void RegisterWwiseObject() override; int SetPlacementFromParent( const Vector3& front, const Vector3& top, const Vector3& position ) override; + + /// Registers the listener with Wwise Spatial Audio if both are initialized. + void RegisterWithSpatialAudio(); }; TYPEDEF_BLUECLASS( AudListener ); diff --git a/src/AudManager.cpp b/src/AudManager.cpp index 51b4a38..6e912f2 100644 --- a/src/AudManager.cpp +++ b/src/AudManager.cpp @@ -169,15 +169,6 @@ bool AudManager::Init() return false; } - if( m_spatialAudioSettings->GetSpatialAudioGeometryEnabled() ) - { - if( !InitSpatialAudio() ) - { - CCP_LOGERR( "Failed to initialize Spatial Audio Geometry" ); - return false; - } - } - #ifndef AK_OPTIMIZED if( !InitCommunication() ) { @@ -476,7 +467,7 @@ bool AudManager::EnsureSpatialAudio() if( AudListenerPtr listener = GetListener() ) { - AK::SpatialAudio::RegisterListener( listener->GetID() ); + listener->RegisterWithSpatialAudio(); } return true; } @@ -1003,14 +994,14 @@ void AudManager::Enable( BankVector soundBanksToLoad ) LoadBank( *it ); } + m_roomManager->ResendAll(); + auto objects = m_soundPrioritization->GetPrioritizedAudioObjects(); for( auto obj : objects ) { obj->Wake(); } - m_roomManager->ResendAll(); - BeOS->RegisterForTicks( this, (void*)"Audio::Tick" ); return; } diff --git a/vcpkg.json b/vcpkg.json index 4ea3fab..1543dc9 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -10,7 +10,7 @@ }, { "name": "carbon-trinityaudioapi", - "version>=": "3.0.0" + "version>=": "3.1.0" }, { "name": "wwise",