From b8632bb00a249f98baccb6c6bc695747b81aef21 Mon Sep 17 00:00:00 2001
From: Rye Mutt <rye@alchemyviewer.org>
Date: Wed, 10 Nov 2021 23:17:07 -0500
Subject: [PATCH] Add sound explorer

---
 indra/llaudio/llaudioengine.cpp               |  95 +++-
 indra/llaudio/llaudioengine.h                 |  55 +-
 indra/newview/CMakeLists.txt                  |   2 +
 indra/newview/alfloaterexploresounds.cpp      | 530 ++++++++++++++++++
 indra/newview/alfloaterexploresounds.h        |  53 ++
 indra/newview/llviewerfloaterreg.cpp          |   2 +
 indra/newview/llviewermessage.cpp             |   2 +-
 .../default/xui/en/floater_explore_sounds.xml |  55 ++
 .../skins/default/xui/en/menu_viewer.xml      |  11 +
 9 files changed, 792 insertions(+), 13 deletions(-)
 create mode 100644 indra/newview/alfloaterexploresounds.cpp
 create mode 100644 indra/newview/alfloaterexploresounds.h
 create mode 100644 indra/newview/skins/default/xui/en/floater_explore_sounds.xml

diff --git a/indra/llaudio/llaudioengine.cpp b/indra/llaudio/llaudioengine.cpp
index f3c49422371..238fbed1d96 100644
--- a/indra/llaudio/llaudioengine.cpp
+++ b/indra/llaudio/llaudioengine.cpp
@@ -46,7 +46,6 @@ extern void request_sound(const LLUUID &sound_guid);
 
 LLAudioEngine* gAudiop = NULL;
 
-
 //
 // LLAudioEngine implementation
 //
@@ -805,7 +804,7 @@ F64 LLAudioEngine::mapWindVecToPan(const LLVector3& wind_vec)
 
 
 void LLAudioEngine::triggerSound(const LLUUID &audio_uuid, const LLUUID& owner_id, const F32 gain,
-								 const S32 type, const LLVector3d &pos_global)
+								 const S32 type, const LLVector3d &pos_global, const LLUUID& source_object, const LLUUID& audio_source_id)
 {
 	// Create a new source (since this can't be associated with an existing source.
 	//LL_INFOS() << "Localized: " << audio_uuid << LL_ENDL;
@@ -816,10 +815,13 @@ void LLAudioEngine::triggerSound(const LLUUID &audio_uuid, const LLUUID& owner_i
 		return;
 	}
 
-	LLUUID source_id;
-	source_id.generate();
+	LLUUID source_id = audio_source_id;
+	if (source_id.isNull())
+	{
+		source_id.generate();
+	}
 
-	LLAudioSource *asp = new LLAudioSource(source_id, owner_id, gain, type);
+	LLAudioSource *asp = new LLAudioSource(source_id, owner_id, gain, type, source_object, true);
 	addAudioSource(asp);
 	if (pos_global.isExactlyZero())
 	{
@@ -1269,13 +1271,73 @@ void LLAudioEngine::assetCallback(const LLUUID &uuid, LLAssetType::EType type, v
 	gAudiop->startNextTransfer();
 }
 
+void LLAudioEngine::logSoundPlay(const LLUUID& id, LLVector3d position, S32 type, const LLUUID& assetid, const LLUUID& ownerid, const LLUUID& sourceid, bool is_trigger, bool is_looped)
+{
+	pruneSoundLog();
+	if (mSoundHistory.size() > 2048)
+		return; // Might clear out oldest entries before giving up?
+
+	LLSoundHistoryItem item;
+	item.mID = id;
+	item.mPosition = position;
+	item.mType = type;
+	item.mAssetID = assetid;
+	item.mOwnerID = ownerid;
+	item.mSourceID = sourceid;
+	item.mPlaying = true;
+	item.mTimeStarted = LLTimer::getElapsedSeconds();
+	item.mTimeStopped = F64_MAX;
+	item.mIsTrigger = is_trigger;
+	item.mIsLooped = is_looped;
+
+	item.mReviewed = false;
+	item.mReviewedCollision = false;
+
+	mSoundHistory.insert_or_assign(id, item);
+}
+
+void LLAudioEngine::logSoundStop(const LLUUID& id)
+{
+	auto iter = mSoundHistory.find(id);
+	if (iter != mSoundHistory.end())
+	{
+		LLSoundHistoryItem& hist_item = iter->second;
+		hist_item.mPlaying = false;
+		hist_item.mTimeStopped = LLTimer::getElapsedSeconds();
+		pruneSoundLog();
+	}
+}
+
+void LLAudioEngine::pruneSoundLog()
+{
+	if (++mSoundHistoryPruneCounter >= 64)
+	{
+		mSoundHistoryPruneCounter = 0;
+		while (mSoundHistory.size() > 256)
+		{
+			auto iter = mSoundHistory.begin();
+			auto end = mSoundHistory.end();
+			U64 lowest_time = (*iter).second.mTimeStopped;
+			LLUUID lowest_id = (*iter).first;
+			for (; iter != end; ++iter)
+			{
+				if ((*iter).second.mTimeStopped < lowest_time)
+				{
+					lowest_time = (*iter).second.mTimeStopped;
+					lowest_id = (*iter).first;
+				}
+			}
+			mSoundHistory.erase(lowest_id);
+		}
+	}
+}
 
 //
 // LLAudioSource implementation
 //
 
 
-LLAudioSource::LLAudioSource(const LLUUID& id, const LLUUID& owner_id, const F32 gain, const S32 type)
+LLAudioSource::LLAudioSource(const LLUUID& id, const LLUUID& owner_id, const F32 gain, const S32 type, const LLUUID& source_id, const bool isTrigger)
 :	mID(id),
 	mOwnerID(owner_id),
 	mPriority(0.f),
@@ -1291,11 +1353,13 @@ LLAudioSource::LLAudioSource(const LLUUID& id, const LLUUID& owner_id, const F32
 	mType(type),
 	mChannelp(NULL),
 	mCurrentDatap(NULL),
-	mQueuedDatap(NULL)
+	mQueuedDatap(NULL),
+	mSourceID(source_id),
+	mIsTrigger(isTrigger)
 {
+	mLogID.generate();
 }
 
-
 LLAudioSource::~LLAudioSource()
 {
 	if (mChannelp)
@@ -1304,6 +1368,11 @@ LLAudioSource::~LLAudioSource()
 		mChannelp->setSource(NULL);
 		mChannelp = NULL;
 	}
+
+	if (mType != LLAudioEngine::AUDIO_TYPE_UI) // && mSourceID.notNull())
+	{
+		gAudiop->logSoundStop(mLogID);
+	}
 }
 
 
@@ -1413,12 +1482,18 @@ bool LLAudioSource::setupChannel()
 
 bool LLAudioSource::play(const LLUUID &audio_uuid)
 {
+	if(mType != LLAudioEngine::AUDIO_TYPE_UI) //&& mSourceID.notNull())
+	{
+		gAudiop->logSoundPlay(mLogID, mPositionGlobal, mType, audio_uuid, mOwnerID, mSourceID, mIsTrigger, mLoop);
+	}
+
 	// Special abuse of play(); don't play a sound, but kill it.
 	if (audio_uuid.isNull())
 	{
 		if (getChannel())
 		{
-			getChannel()->setSource(NULL);
+			if(getChannel()->getSource())
+				getChannel()->setSource(NULL);
 			setChannel(NULL);
 			if (!isMuted())
 			{
@@ -1438,6 +1513,8 @@ bool LLAudioSource::play(const LLUUID &audio_uuid)
 	}
 
 	LLAudioData *adp = gAudiop->getAudioData(audio_uuid);
+	if( !adp )
+		return false;
 	addAudioData(adp);
 
 	if (isMuted())
diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h
index f1be608baab..2cc4e3026e2 100644
--- a/indra/llaudio/llaudioengine.h
+++ b/indra/llaudio/llaudioengine.h
@@ -40,6 +40,7 @@
 #include "llextendedstatus.h"
 
 #include "lllistener.h"
+#include "absl/container/node_hash_map.h"
 #include "absl/container/flat_hash_map.h"
 
 const F32 LL_WIND_UPDATE_INTERVAL = 0.1f;
@@ -49,7 +50,7 @@ const F32 ATTACHED_OBJECT_TIMEOUT = 5.0f;
 const F32 DEFAULT_MIN_DISTANCE = 2.0f;
 
 #define MAX_CHANNELS 30
-#define MAX_BUFFERS 40	// Some extra for preloading, maybe?
+#define MAX_BUFFERS 60	// Some extra for preloading, maybe?
 
 class LLAudioSource;
 class LLAudioData;
@@ -58,6 +59,7 @@ class LLAudioChannelOpenAL;
 class LLAudioBuffer;
 class LLStreamingAudioInterface;
 struct SoundData;
+struct LLSoundHistoryItem;
 
 //
 //  LLAudioEngine definition
@@ -133,7 +135,9 @@ class LLAudioEngine
 	// Owner ID is the owner of the object making the request
 	void triggerSound(const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain,
 					  const S32 type = LLAudioEngine::AUDIO_TYPE_NONE,
-					  const LLVector3d &pos_global = LLVector3d::zero);
+					  const LLVector3d &pos_global = LLVector3d::zero,
+					  const LLUUID& source_object = LLUUID::null,
+					  const LLUUID& audio_source_id = LLUUID::null);
 	void triggerSound(const SoundData& soundData);
 
 	bool preloadSound(const LLUUID &id);
@@ -239,6 +243,17 @@ class LLAudioEngine
 
 	LLFrameTimer mWindUpdateTimer;
 
+public:
+	void logSoundPlay(const LLUUID& id, LLVector3d position, S32 type, const LLUUID& assetid, const LLUUID& ownerid, const LLUUID& sourceid, bool is_trigger, bool is_looped);
+	void logSoundStop(const LLUUID& id);
+	void pruneSoundLog();
+
+	auto& getSoundLog() { return mSoundHistory; }
+private:
+	S32 mSoundHistoryPruneCounter = 0;
+
+	using sound_history_map = absl::node_hash_map<LLUUID, LLSoundHistoryItem>;
+	sound_history_map mSoundHistory;
 private:
 	void setDefaults();
 	LLStreamingAudioInterface *mStreamingAudioImpl;
@@ -257,7 +272,7 @@ class LLAudioSource
 public:
 	// owner_id is the id of the agent responsible for making this sound
 	// play, for example, the owner of the object currently playing it
-	LLAudioSource(const LLUUID &id, const LLUUID& owner_id, const F32 gain, const S32 type = LLAudioEngine::AUDIO_TYPE_NONE);
+	LLAudioSource(const LLUUID &id, const LLUUID& owner_id, const F32 gain, const S32 type = LLAudioEngine::AUDIO_TYPE_NONE, const LLUUID& source_id = LLUUID::null, const bool isTrigger = true);
 	virtual ~LLAudioSource();
 
 	virtual void update();						// Update this audio source
@@ -297,6 +312,8 @@ class LLAudioSource
 	virtual void setGain(const F32 gain)							{ mGain = llclamp(gain, 0.f, 1.f); }
 
 	const LLUUID &getID() const		{ return mID; }
+	const LLUUID &getLogID() const { return mLogID; }
+
 	bool isDone() const;
 	bool isMuted() const { return mSourceMuted; }
 
@@ -331,6 +348,9 @@ class LLAudioSource
 	S32             mType;
 	LLVector3d		mPositionGlobal;
 	LLVector3		mVelocity;
+	LLUUID			mLogID;
+	LLUUID			mSourceID;
+	bool			mIsTrigger;
 
 	//LLAudioSource	*mSyncMasterp;	// If we're a slave, the source that we're synced to.
 	LLAudioChannel	*mChannelp;		// If we're currently playing back, this is the channel that we're assigned to.
@@ -473,4 +493,33 @@ struct SoundData
 
 extern LLAudioEngine* gAudiop;
 
+struct LLSoundHistoryItem
+{
+	LLUUID mID;
+	LLVector3d mPosition;
+	S32 mType;
+	bool mPlaying;
+	LLUUID mAssetID;
+	LLUUID mOwnerID;
+	LLUUID mSourceID;
+	bool mIsTrigger;
+	bool mIsLooped;
+	F64 mTimeStarted;
+	F64 mTimeStopped;
+	bool mReviewed;
+	bool mReviewedCollision;
+
+	LLSoundHistoryItem()
+	  : mType(0)
+	  , mPlaying(0)
+	  , mIsTrigger(0)
+	  , mIsLooped(0)
+	  , mTimeStarted(0.f)
+	  , mTimeStopped(0.f)
+	  , mReviewed(false)
+	  , mReviewedCollision(false)
+	{
+	}
+};
+
 #endif
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index aa162b19d47..6541c606637 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -123,6 +123,7 @@ set(viewer_SOURCE_FILES
     alchatcommand.cpp
     alcontrolcache.cpp
     alfloaterao.cpp
+    alfloaterexploresounds.cpp
     alfloaterparticleeditor.cpp
     alfloaterregiontracker.cpp
     alpanelaomini.cpp
@@ -794,6 +795,7 @@ set(viewer_HEADER_FILES
     alchatcommand.h
     alcontrolcache.h
     alfloaterao.h
+    alfloaterexploresounds.h
     alfloaterparticleeditor.h
     alfloaterregiontracker.h
     alpanelaomini.h
diff --git a/indra/newview/alfloaterexploresounds.cpp b/indra/newview/alfloaterexploresounds.cpp
new file mode 100644
index 00000000000..cde36f30637
--- /dev/null
+++ b/indra/newview/alfloaterexploresounds.cpp
@@ -0,0 +1,530 @@
+/** 
+ * @file ALFloaterExploreSounds.cpp
+ */ 
+
+#include "llviewerprecompiledheaders.h"
+
+#include "alfloaterexploresounds.h"
+#include "llcheckboxctrl.h"
+#include "llscrolllistctrl.h"
+#include "llagent.h"
+#include "llagentcamera.h"
+#include "llavatarnamecache.h"
+#include "llviewerobjectlist.h"
+#include "llviewerregion.h"
+#include "llstring.h"
+#include "lltrans.h"
+#include "rlvhandler.h"
+
+static const size_t num_collision_sounds = 28;
+const LLUUID collision_sounds[num_collision_sounds] =
+{
+	LLUUID("dce5fdd4-afe4-4ea1-822f-dd52cac46b08"),
+	LLUUID("51011582-fbca-4580-ae9e-1a5593f094ec"),
+	LLUUID("68d62208-e257-4d0c-bbe2-20c9ea9760bb"),
+	LLUUID("75872e8c-bc39-451b-9b0b-042d7ba36cba"),
+	LLUUID("6a45ba0b-5775-4ea8-8513-26008a17f873"),
+	LLUUID("992a6d1b-8c77-40e0-9495-4098ce539694"),
+	LLUUID("2de4da5a-faf8-46be-bac6-c4d74f1e5767"),
+	LLUUID("6e3fb0f7-6d9c-42ca-b86b-1122ff562d7d"),
+	LLUUID("14209133-4961-4acc-9649-53fc38ee1667"),
+	LLUUID("bc4a4348-cfcc-4e5e-908e-8a52a8915fe6"),
+	LLUUID("9e5c1297-6eed-40c0-825a-d9bcd86e3193"),
+	LLUUID("e534761c-1894-4b61-b20c-658a6fb68157"),
+	LLUUID("8761f73f-6cf9-4186-8aaa-0948ed002db1"),
+	LLUUID("874a26fd-142f-4173-8c5b-890cd846c74d"),
+	LLUUID("0e24a717-b97e-4b77-9c94-b59a5a88b2da"),
+	LLUUID("75cf3ade-9a5b-4c4d-bb35-f9799bda7fb2"),
+	LLUUID("153c8bf7-fb89-4d89-b263-47e58b1b4774"),
+	LLUUID("55c3e0ce-275a-46fa-82ff-e0465f5e8703"),
+	LLUUID("24babf58-7156-4841-9a3f-761bdbb8e237"),
+	LLUUID("aca261d8-e145-4610-9e20-9eff990f2c12"),
+	LLUUID("0642fba6-5dcf-4d62-8e7b-94dbb529d117"),
+	LLUUID("25a863e8-dc42-4e8a-a357-e76422ace9b5"),
+	LLUUID("9538f37c-456e-4047-81be-6435045608d4"),
+	LLUUID("8c0f84c3-9afd-4396-b5f5-9bca2c911c20"),
+	LLUUID("be582e5d-b123-41a2-a150-454c39e961c8"),
+	LLUUID("c70141d4-ba06-41ea-bcbc-35ea81cb8335"),
+	LLUUID("7d1826f4-24c4-4aac-8c2e-eff45df37783"),
+	LLUUID("063c97d3-033a-4e9b-98d8-05c8074922cb")
+};
+
+ALFloaterExploreSounds::ALFloaterExploreSounds(const LLSD& key)
+:	LLFloater(key), LLEventTimer(0.25f)
+{
+}
+
+ALFloaterExploreSounds::~ALFloaterExploreSounds()
+{
+	for (blacklist_avatar_name_cache_connection_map_t::iterator it = mBlacklistAvatarNameCacheConnections.begin(); it != mBlacklistAvatarNameCacheConnections.end(); ++it)
+	{
+		if (it->second.connected())
+		{
+			it->second.disconnect();
+		}
+	}
+	mBlacklistAvatarNameCacheConnections.clear();
+
+	mLocalPlayingAudioSourceIDs.clear();
+}
+
+BOOL ALFloaterExploreSounds::postBuild()
+{
+	getChild<LLButton>("play_locally_btn")->setClickedCallback(boost::bind(&ALFloaterExploreSounds::handlePlayLocally, this));
+	getChild<LLButton>("look_at_btn")->setClickedCallback(boost::bind(&ALFloaterExploreSounds::handleLookAt, this));
+	getChild<LLButton>("stop_btn")->setClickedCallback(boost::bind(&ALFloaterExploreSounds::handleStop, this));
+	getChild<LLButton>("bl_btn")->setClickedCallback(boost::bind(&ALFloaterExploreSounds::blacklistSound, this));
+	getChild<LLButton>("stop_locally_btn")->setClickedCallback(boost::bind(&ALFloaterExploreSounds::handleStopLocally, this));
+
+	mHistoryScroller = getChild<LLScrollListCtrl>("sound_list");
+	mHistoryScroller->setCommitCallback(boost::bind(&ALFloaterExploreSounds::handleSelection, this));
+	mHistoryScroller->setDoubleClickCallback(boost::bind(&ALFloaterExploreSounds::handlePlayLocally, this));
+	mHistoryScroller->sortByColumn("playing", TRUE);
+
+	mCollisionSounds = getChild<LLCheckBoxCtrl>("collision_chk");
+	mRepeatedAssets = getChild<LLCheckBoxCtrl>("repeated_asset_chk");
+	mAvatarSounds = getChild<LLCheckBoxCtrl>("avatars_chk");
+	mObjectSounds = getChild<LLCheckBoxCtrl>("objects_chk");
+	mPaused = getChild<LLCheckBoxCtrl>("pause_chk");
+
+	return TRUE;
+}
+
+void ALFloaterExploreSounds::handleSelection()
+{
+	size_t num_selected = mHistoryScroller->getAllSelected().size();
+	bool multiple = (num_selected > 1);
+	childSetEnabled("look_at_btn", (num_selected && !multiple));
+	childSetEnabled("play_locally_btn", num_selected);
+	childSetEnabled("stop_btn", num_selected);
+	childSetEnabled("bl_btn", num_selected);
+}
+
+LLSoundHistoryItem ALFloaterExploreSounds::getItem(const LLUUID& itemID)
+{
+	if (!gAudiop)
+	{
+		LLSoundHistoryItem item;
+		item.mID = LLUUID::null;
+		return item;
+	}
+
+	const auto& sound_log = gAudiop->getSoundLog();
+	auto found = sound_log.find(itemID);
+	if (found != sound_log.end())
+	{
+		return found->second;
+	}
+	else
+	{
+		// If log is paused, hopefully we can find it in mLastHistory
+		std::list<LLSoundHistoryItem>::iterator iter = mLastHistory.begin();
+		std::list<LLSoundHistoryItem>::iterator end = mLastHistory.end();
+		for ( ; iter != end; ++iter)
+		{
+			if ((*iter).mID == itemID)
+			{
+				return (*iter);
+			}
+		}
+	}
+	LLSoundHistoryItem item;
+	item.mID = LLUUID::null;
+	return item;
+}
+
+class LLSoundHistoryItemCompare
+{
+public:
+	bool operator() (LLSoundHistoryItem first, LLSoundHistoryItem second)
+	{
+		if (first.mPlaying)
+		{
+			if (second.mPlaying)
+			{
+				return (first.mTimeStarted > second.mTimeStarted);
+			}
+			else
+			{
+				return true;
+			}
+		}
+		else if (second.mPlaying)
+		{
+			return false;
+		}
+		else
+		{
+			return (first.mTimeStopped > second.mTimeStopped);
+		}
+	}
+};
+
+BOOL ALFloaterExploreSounds::tick()
+{
+	static const std::string str_playing =  getString("Playing");
+	static LLUIString str_not_playing = getString("NotPlaying");
+	static const std::string str_type_ui =  getString("Type_UI");
+	static const std::string str_type_avatar = getString("Type_Avatar");
+	static const std::string str_type_trigger_sound = getString("Type_llTriggerSound");
+	static const std::string str_type_loop_sound = getString("Type_llLoopSound");
+	static const std::string str_type_play_sound = getString("Type_llPlaySound");
+	static const std::string str_unknown_name = LLTrans::getString("AvatarNameWaiting");
+
+	bool show_collision_sounds = mCollisionSounds->get();
+	bool show_repeated_assets = mRepeatedAssets->get();
+	bool show_avatars = mAvatarSounds->get();
+	bool show_objects = mObjectSounds->get();
+
+	std::list<LLSoundHistoryItem> history;
+	if (mPaused->get())
+	{
+		history = mLastHistory;
+	}
+	else
+	{
+		if (gAudiop)
+		{
+			for (const auto& sound_pair : gAudiop->getSoundLog())
+			{
+				history.push_back(sound_pair.second);
+			}
+			LLSoundHistoryItemCompare c;
+			history.sort(c);
+		}
+		mLastHistory = history;
+	}
+
+	// Save scroll pos and selection so they can be restored
+	S32 scroll_pos = mHistoryScroller->getScrollPos();
+	uuid_vec_t selected_ids;
+	std::vector<LLScrollListItem*> selected_items = mHistoryScroller->getAllSelected();
+	std::vector<LLScrollListItem*>::iterator selection_iter = selected_items.begin();
+	std::vector<LLScrollListItem*>::iterator selection_end = selected_items.end();
+	for (; selection_iter != selection_end; ++selection_iter)
+	{
+		selected_ids.push_back((*selection_iter)->getUUID());
+	}
+
+	mHistoryScroller->clearRows();
+
+	std::list<LLUUID> unique_asset_list;
+
+	std::list<LLSoundHistoryItem>::iterator iter = history.begin();
+	std::list<LLSoundHistoryItem>::iterator end = history.end();
+	for ( ; iter != end; ++iter)
+	{
+		LLSoundHistoryItem item = (*iter);
+
+		bool is_avatar = item.mOwnerID == item.mSourceID;
+		if (is_avatar && !show_avatars)
+		{
+			continue;
+		}
+
+		bool is_object = !is_avatar;
+		if (is_object && !show_objects)
+		{
+			continue;
+		}
+
+		bool is_repeated_asset = std::find(unique_asset_list.begin(), unique_asset_list.end(), item.mAssetID) != unique_asset_list.end();
+		if (is_repeated_asset && !show_repeated_assets)
+		{
+			continue;
+		}
+
+		if (!item.mReviewed)
+		{
+			item.mReviewedCollision = std::find(&collision_sounds[0], &collision_sounds[num_collision_sounds], item.mAssetID) != &collision_sounds[num_collision_sounds];
+			item.mReviewed = true;
+		}
+
+		bool is_collision_sound = item.mReviewedCollision;
+		if (is_collision_sound && !show_collision_sounds)
+		{
+			continue;
+		}
+
+		unique_asset_list.push_back(item.mAssetID);
+
+		LLSD element;
+		element["id"] = item.mID;
+
+		LLSD& playing_column = element["columns"][0];
+		playing_column["column"] = "playing";
+		if (item.mPlaying)
+		{
+			playing_column["value"] = " " + str_playing;
+		}
+		else
+		{
+			LLStringUtil::format_map_t format_args;
+			format_args["TIME"] = llformat("%.1f", static_cast<F32>((LLTimer::getElapsedSeconds() - item.mTimeStopped) / 60.0));
+			str_not_playing.setArgs(format_args);
+			playing_column["value"] = str_not_playing.getString();
+		}
+
+		LLSD& type_column = element["columns"][1];
+		type_column["column"] = "type";
+		if (item.mType == LLAudioEngine::AUDIO_TYPE_UI)
+		{
+			// this shouldn't happen for now, as UI is forbidden in the log
+			type_column["value"] = str_type_ui;
+		}
+		else
+		{
+			std::string type;
+
+			if (is_avatar)
+			{
+				type = str_type_avatar;
+			}
+			else
+			{
+				if (item.mIsTrigger)
+				{
+					type = str_type_trigger_sound;
+				}
+				else
+				{
+					if (item.mIsLooped)
+					{
+						type = str_type_loop_sound;
+					}
+					else
+					{
+						type = str_type_play_sound;
+					}
+				}
+			}
+
+			type_column["value"] = type;
+		}
+
+		LLSD& owner_column = element["columns"][2];
+		owner_column["column"] = "owner";
+		LLAvatarName av_name;
+		if (LLAvatarNameCache::get(item.mOwnerID, &av_name))
+		{
+			owner_column["value"] = !gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES) ? av_name.getCompleteName() : RlvStrings::getAnonym(av_name);
+		}
+		else
+		{
+			owner_column["value"] = str_unknown_name;
+		}
+
+		LLSD& sound_column = element["columns"][3];
+		sound_column["column"] = "sound";
+		sound_column["value"] = item.mAssetID.asString().substr(0,16);
+
+		mHistoryScroller->addElement(element, ADD_BOTTOM);
+	}
+
+	mHistoryScroller->selectMultiple(selected_ids);
+	mHistoryScroller->setScrollPos(scroll_pos);
+
+	// Clean up stopped local audio source IDs
+	uuid_vec_t stopped_audio_src_ids;
+	uuid_vec_t::iterator audio_src_id_iter = mLocalPlayingAudioSourceIDs.begin();
+	uuid_vec_t::iterator audio_src_id_end = mLocalPlayingAudioSourceIDs.end();
+	for (; audio_src_id_iter != audio_src_id_end; ++audio_src_id_iter)
+	{
+		LLUUID audio_src_id = *audio_src_id_iter;
+		LLAudioSource* audio_source = gAudiop->findAudioSource(audio_src_id);
+		if (!audio_source || audio_source->isDone())
+		{
+			stopped_audio_src_ids.push_back(audio_src_id);
+		}
+	}
+
+	for (uuid_vec_t::iterator stopped_audio_src_ids_iter = stopped_audio_src_ids.begin();
+		 stopped_audio_src_ids_iter != stopped_audio_src_ids.end(); ++stopped_audio_src_ids_iter)
+	{
+		uuid_vec_t::iterator find_iter = std::find(mLocalPlayingAudioSourceIDs.begin(), mLocalPlayingAudioSourceIDs.end(), *stopped_audio_src_ids_iter);
+		if (find_iter != mLocalPlayingAudioSourceIDs.end())
+		{
+			mLocalPlayingAudioSourceIDs.erase(find_iter);
+		}
+	}
+
+	childSetEnabled("stop_locally_btn", mLocalPlayingAudioSourceIDs.size() > 0);
+
+	return FALSE;
+}
+
+void ALFloaterExploreSounds::handlePlayLocally()
+{
+	std::vector<LLScrollListItem*> selection = mHistoryScroller->getAllSelected();
+	std::vector<LLScrollListItem*>::iterator selection_iter = selection.begin();
+	std::vector<LLScrollListItem*>::iterator selection_end = selection.end();
+	uuid_vec_t asset_list;
+	for ( ; selection_iter != selection_end; ++selection_iter)
+	{
+		LLSoundHistoryItem item = getItem((*selection_iter)->getValue());
+		if (item.mID.isNull())
+		{
+			continue;
+		}
+
+		// Unique assets only
+		if (std::find(asset_list.begin(), asset_list.end(), item.mAssetID) == asset_list.end())
+		{
+			asset_list.push_back(item.mAssetID);
+			LLUUID audio_source_id = LLUUID::generateNewID();
+			gAudiop->triggerSound(item.mAssetID, gAgent.getID(), 1.0f, LLAudioEngine::AUDIO_TYPE_UI, LLVector3d::zero, LLUUID::null, audio_source_id);
+			mLocalPlayingAudioSourceIDs.push_back(audio_source_id);
+		}
+	}
+
+	childSetEnabled("stop_locally_btn", mLocalPlayingAudioSourceIDs.size() > 0);
+}
+
+void ALFloaterExploreSounds::handleLookAt()
+{
+	LLUUID selection = mHistoryScroller->getSelectedValue().asUUID();
+	LLSoundHistoryItem item = getItem(selection); // Single item only
+	if (item.mID.isNull())
+	{
+		return;
+	}
+
+	LLVector3d pos_global = item.mPosition;
+
+	// Try to find object position
+	if (item.mSourceID.notNull())
+	{
+		LLViewerObject* object = gObjectList.findObject(item.mSourceID);
+		if (object)
+		{
+			pos_global = object->getPositionGlobal();
+		}
+	}
+
+	// Move the camera
+	// Find direction to self (reverse)
+	LLVector3d cam = gAgent.getPositionGlobal() - pos_global;
+	cam.normalize();
+	// Go 4 meters back and 3 meters up
+	cam *= 4.0;
+	cam += pos_global;
+	cam += LLVector3d(0.0, 0.0, 3.0);
+
+	gAgentCamera.setFocusOnAvatar(FALSE, FALSE);
+	gAgentCamera.setCameraPosAndFocusGlobal(cam, pos_global, item.mSourceID);
+	gAgentCamera.setCameraAnimating(FALSE);
+}
+
+void ALFloaterExploreSounds::handleStop()
+{
+	if (!gAudiop)
+		return;
+
+	auto& sound_log = gAudiop->getSoundLog();
+
+	std::vector<LLScrollListItem*> selection = mHistoryScroller->getAllSelected();
+	for (const auto& selection_item : selection)
+	{
+		LLSoundHistoryItem item = getItem(selection_item->getValue());
+		if (item.mID.notNull() && item.mPlaying)
+		{
+			LLAudioSource* audio_source = gAudiop->findAudioSource(item.mSourceID);
+			if (audio_source)
+			{
+				S32 type = item.mType;
+				audio_source->setType(LLAudioEngine::AUDIO_TYPE_UI);
+				audio_source->play(LLUUID::null);
+				audio_source->setType(type);
+			}
+			else
+			{
+				LL_WARNS("SoundExplorer") << "audio source for source ID " << item.mSourceID << " already gone but still marked as playing. Fixing ..." << LL_ENDL;
+				auto iter = sound_log.find(item.mID);
+				if (iter != sound_log.end())
+				{
+					iter->second.mPlaying = false;
+					iter->second.mTimeStopped = LLTimer::getElapsedSeconds();
+				}
+				else
+				{
+					for (auto& histItem : mLastHistory)
+					{
+						if (histItem.mID == item.mID)
+						{
+							histItem.mPlaying = false;
+							histItem.mTimeStopped = LLTimer::getElapsedSeconds();
+							break;
+						}
+					}
+				}
+				continue;
+			}
+		}
+	}
+}
+
+void ALFloaterExploreSounds::handleStopLocally()
+{
+	uuid_vec_t::iterator audio_source_id_iter = mLocalPlayingAudioSourceIDs.begin();
+	uuid_vec_t::iterator audio_source_id_end = mLocalPlayingAudioSourceIDs.end();
+	for (; audio_source_id_iter != audio_source_id_end; ++audio_source_id_iter)
+	{
+		LLUUID audio_source_id = *audio_source_id_iter;
+		LLAudioSource* audio_source = gAudiop->findAudioSource(audio_source_id);
+		if (audio_source && !audio_source->isDone())
+		{
+			audio_source->play(LLUUID::null);
+		}
+	}
+
+	mLocalPlayingAudioSourceIDs.clear();
+}
+
+//add sound to blacklist
+void ALFloaterExploreSounds::blacklistSound()
+{
+	std::vector<LLScrollListItem*> selection = mHistoryScroller->getAllSelected();
+	std::vector<LLScrollListItem*>::iterator selection_iter = selection.begin();
+	std::vector<LLScrollListItem*>::iterator selection_end = selection.end();
+
+	for ( ; selection_iter != selection_end; ++selection_iter)
+	{
+		LLSoundHistoryItem item = getItem((*selection_iter)->getValue());
+		if (item.mID.isNull())
+		{
+			continue;
+		}
+
+		std::string region_name;
+		LLViewerRegion* cur_region = gAgent.getRegion();
+		if (cur_region)
+		{
+			region_name = cur_region->getName();
+		}
+
+		blacklist_avatar_name_cache_connection_map_t::iterator it = mBlacklistAvatarNameCacheConnections.find(item.mOwnerID);
+		if (it != mBlacklistAvatarNameCacheConnections.end())
+		{
+			if (it->second.connected())
+			{
+				it->second.disconnect();
+			}
+			mBlacklistAvatarNameCacheConnections.erase(it);
+		}
+		LLAvatarNameCache::callback_connection_t cb = LLAvatarNameCache::get(item.mOwnerID, boost::bind(&ALFloaterExploreSounds::onBlacklistAvatarNameCacheCallback, this, _1, _2, item.mAssetID, region_name));
+		mBlacklistAvatarNameCacheConnections.insert(std::make_pair(item.mOwnerID, cb));
+	}
+}
+
+void ALFloaterExploreSounds::onBlacklistAvatarNameCacheCallback(const LLUUID& av_id, const LLAvatarName& av_name, const LLUUID& asset_id, const std::string& region_name)
+{
+	blacklist_avatar_name_cache_connection_map_t::iterator it = mBlacklistAvatarNameCacheConnections.find(av_id);
+	if (it != mBlacklistAvatarNameCacheConnections.end())
+	{
+		if (it->second.connected())
+		{
+			it->second.disconnect();
+		}
+		mBlacklistAvatarNameCacheConnections.erase(it);
+	}
+}
diff --git a/indra/newview/alfloaterexploresounds.h b/indra/newview/alfloaterexploresounds.h
new file mode 100644
index 00000000000..657e1547bc6
--- /dev/null
+++ b/indra/newview/alfloaterexploresounds.h
@@ -0,0 +1,53 @@
+/** 
+ * @file alfloaterexploresounds.h
+ */
+
+#ifndef AL_ALFLOATEREXPLORESOUNDS_H
+#define AL_ALFLOATEREXPLORESOUNDS_H
+
+#include "llfloater.h"
+#include "lleventtimer.h"
+#include "llaudioengine.h"
+#include "llavatarnamecache.h"
+
+class LLCheckBoxCtrl;
+class LLScrollListCtrl;
+
+class ALFloaterExploreSounds final
+: public LLFloater, public LLEventTimer
+{
+public:
+	ALFloaterExploreSounds(const LLSD& key);
+	BOOL postBuild();
+
+	BOOL tick();
+
+	LLSoundHistoryItem getItem(const LLUUID& itemID);
+
+private:
+	virtual ~ALFloaterExploreSounds();
+	void handlePlayLocally();
+	void handleLookAt();
+	void handleStop();
+	void handleStopLocally();
+	void handleSelection();
+	void blacklistSound();
+
+	LLScrollListCtrl*	mHistoryScroller;
+	LLCheckBoxCtrl*		mCollisionSounds;
+	LLCheckBoxCtrl*		mRepeatedAssets;
+	LLCheckBoxCtrl*		mAvatarSounds;
+	LLCheckBoxCtrl*		mObjectSounds;
+	LLCheckBoxCtrl*		mPaused;
+
+	std::list<LLSoundHistoryItem> mLastHistory;
+
+	uuid_vec_t mLocalPlayingAudioSourceIDs;
+
+	typedef std::map<LLUUID, boost::signals2::connection> blacklist_avatar_name_cache_connection_map_t;
+	blacklist_avatar_name_cache_connection_map_t mBlacklistAvatarNameCacheConnections;
+
+	void onBlacklistAvatarNameCacheCallback(const LLUUID& av_id, const LLAvatarName& av_name, const LLUUID& asset_id, const std::string& region_name);
+};
+
+#endif
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index e85a3b8a9bc..ccd1df31ca4 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -32,6 +32,7 @@
 #include "llviewerfloaterreg.h"
 
 #include "alfloaterao.h"
+#include "alfloaterexploresounds.h"
 #include "alfloaterparticleeditor.h"
 #include "alfloaterregiontracker.h"
 #include "llcommandhandler.h"
@@ -418,6 +419,7 @@ void LLViewerFloaterReg::registerFloaters()
 	LLFloaterReg::add("particle_editor", "floater_particle_editor.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ALFloaterParticleEditor>);
 	LLFloaterReg::add("quick_settings", "floater_quick_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloater>);
 	LLFloaterReg::add("region_tracker", "floater_region_tracker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ALFloaterRegionTracker>);
+	LLFloaterReg::add("sound_explorer", "floater_explore_sounds.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ALFloaterExploreSounds>);
 	
 	LLFloaterReg::registerControlVariables(); // Make sure visibility and rect controls get preserved when saving
 }
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 35996663f5f..d7554022d68 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -4146,7 +4146,7 @@ void process_sound_trigger(LLMessageSystem *msg, void **)
 		return;
 	}
 
-	gAudiop->triggerSound(sound_id, owner_id, gain, LLAudioEngine::AUDIO_TYPE_SFX, pos_global);
+	gAudiop->triggerSound(sound_id, owner_id, gain, LLAudioEngine::AUDIO_TYPE_SFX, pos_global, object_id);
 }
 
 void process_preload_sound(LLMessageSystem *msg, void **user_data)
diff --git a/indra/newview/skins/default/xui/en/floater_explore_sounds.xml b/indra/newview/skins/default/xui/en/floater_explore_sounds.xml
new file mode 100644
index 00000000000..26e0e6adac7
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_explore_sounds.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ can_close="true"
+ can_minimize="true"
+ positioning="centered"
+ can_resize="true"
+ height="300"
+ width="550"
+ min_width="550"
+ min_height="170"
+ name="sound_explorer"
+ title="Sounds"
+ save_rect="true"
+ save_visibility="true"
+ single_instance="true"
+ help_topic="fs_sound_explorer">
+	<floater.string name="Playing">
+		Playing
+	</floater.string>
+	<floater.string name="NotPlaying">
+		[TIME] min ago
+	</floater.string>
+	<floater.string name="Type_UI">
+		UI
+	</floater.string>
+	<floater.string name="Type_Avatar">
+		Avatar
+	</floater.string>
+	<floater.string name="Type_llTriggerSound">
+		llTriggerSound
+	</floater.string>
+	<floater.string name="Type_llLoopSound">
+		llLoopSound
+	</floater.string>
+	<floater.string name="Type_llPlaySound">
+		llPlaySound
+	</floater.string>
+	<check_box follows="top|left" bottom_delta="20" left="5" width="64" name="avatars_chk" label="Avatars" />
+	<check_box follows="top|left" bottom_delta="0" left_delta="64" width="64" name="objects_chk" label="Objects" initial_value="true" />
+	<check_box follows="top|left" bottom_delta="0" left_delta="64" width="160" name="collision_chk" label="Default Collision Sounds" />
+	<check_box follows="top|left" bottom_delta="0" left_delta="160" width="110" name="repeated_asset_chk" label="Repeated Asset" />
+	<check_box follows="top|left" bottom_delta="0" left_delta="110" width="80" name="pause_chk" label="Pause Log" initial_value="false"/>
+	<scroll_list column_padding="0" draw_heading="true" follows="all" left="10" multi_select="true" name="sound_list" search_column="0" top="40" right="-10" bottom="-60">
+		<columns dynamicwidth="false" width="80" label="Playing" name="playing" />
+		<columns dynamicwidth="false" width="100" label="Type" name="type" />
+		<columns dynamicwidth="true" label="Owner" name="owner" />
+		<columns dynamicwidth="false" width="0" label="Sound" name="sound" />
+	</scroll_list>
+	<button bottom_delta="25" follows="left|bottom" height="20" label="Play Locally" name="play_locally_btn" left="10" width="95" enabled="false"/>
+	<button bottom_delta="0" follows="left|bottom" height="20" label="Stop Locally" name="stop_locally_btn" left_delta="100" width="95" enabled="false"/>
+	<button bottom_delta="0" follows="left|bottom" height="20" label="Look At" name="look_at_btn" left_delta="100" width="95" enabled="false"/>
+
+	<button bottom_delta="0" follows="left|bottom" height="20" label="Stop" name="stop_btn" right="-10" width="95" enabled="false"/>
+	<button bottom_delta="0" follows="left|bottom" height="20" label="Blacklist" name="bl_btn" right="-110" width="95" enabled="false" visible="false"/>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 598fef6d46f..e34d6f5624f 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -687,6 +687,17 @@
           <menu_item_call.on_click
            function="World.SyncAnimations" />
         </menu_item_call>
+		<menu_item_check
+         label="Sound Explorer"
+         name="Sound Explorer"
+         use_mac_ctrl="true">
+			<menu_item_check.on_check
+             function="Floater.Visible"
+             parameter="sound_explorer" />
+			<menu_item_check.on_click
+             function="Floater.Toggle"
+             parameter="sound_explorer" />
+		</menu_item_check>
         <menu_item_separator/>
         <menu_item_call
          label="Landmark This Place"
-- 
GitLab