diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 7094d6829259515cbc2c4502560ac750424a07a5..fe2aef9ef885977decc0454a894972d283e713a4 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -216,6 +216,7 @@ set(viewer_SOURCE_FILES
     llfloaterurldisplay.cpp
     llfloaterurlentry.cpp
     llfloatervoicedevicesettings.cpp
+    llfloatervoiceeffect.cpp
     llfloaterwater.cpp
     llfloaterwhitelistentry.cpp
     llfloaterwindlight.cpp
@@ -355,6 +356,7 @@ set(viewer_SOURCE_FILES
     llpanelprofileview.cpp
     llpanelteleporthistory.cpp
     llpaneltiptoast.cpp
+    llpanelvoiceeffect.cpp
     llpanelvolume.cpp
     llpanelvolumepulldown.cpp
     llparcelselection.cpp
@@ -734,6 +736,7 @@ set(viewer_HEADER_FILES
     llfloaterurldisplay.h
     llfloaterurlentry.h
     llfloatervoicedevicesettings.h
+    llfloatervoiceeffect.h
     llfloaterwater.h
     llfloaterwhitelistentry.h
     llfloaterwindlight.h
@@ -868,6 +871,7 @@ set(viewer_HEADER_FILES
     llpanelprofileview.h
     llpanelteleporthistory.h
     llpaneltiptoast.h
+    llpanelvoiceeffect.h
     llpanelvolume.h
     llpanelvolumepulldown.h
     llparcelselection.h
diff --git a/indra/newview/app_settings/logcontrol.xml b/indra/newview/app_settings/logcontrol.xml
index d7bb64ce8a7787b39abc4bf24ebfc954571cdfdb..d3fb958638434e558102a5307666b20b079f8d18 100644
--- a/indra/newview/app_settings/logcontrol.xml
+++ b/indra/newview/app_settings/logcontrol.xml
@@ -40,6 +40,7 @@
 						</array>
 					<key>tags</key>
 						<array>
+							<string>Voice</string>
 						</array>
 				</map>
 			</array>
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 1d27d00451b83f3528b2693b58f8124945626399..bd166eb291d97eff86f13d9b3b2f175376bb6e9a 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -10595,6 +10595,28 @@
       <key>Value</key>
       <integer>0</integer>
     </map>
+    <key>VoiceEffectExpiryWarningTime</key>
+    <map>
+      <key>Comment</key>
+      <string>How much notice to give of voice effect subscriptions expiry, in seconds.</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>S32</string>
+      <key>Value</key>
+      <integer>259200</integer>
+    </map>
+    <key>VoiceMorphingEnabled</key>
+    <map>
+      <key>Comment</key>
+      <string>Whether or not to enable Voice Effects and show the UI.</string>
+      <key>Persist</key>
+      <integer>0</integer>
+      <key>Type</key>
+      <string>Boolean</string>
+      <key>Value</key>
+      <integer>1</integer>
+    </map>
     <key>AutoDisengageMic</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/app_settings/settings_per_account.xml b/indra/newview/app_settings/settings_per_account.xml
index 3ce32a05b0de4dd9e3094c516a679145b351968a..c94ae1fca1e183cae8d2117f3ebc3a38cbc137d6 100644
--- a/indra/newview/app_settings/settings_per_account.xml
+++ b/indra/newview/app_settings/settings_per_account.xml
@@ -99,6 +99,17 @@
         <key>Value</key>
             <integer>1</integer>
         </map>
+    <key>VoiceEffectDefault</key>
+    <map>
+        <key>Comment</key>
+            <string>Selected voice effect</string>
+        <key>Persist</key>
+            <integer>1</integer>
+        <key>Type</key>
+            <string>String</string>
+        <key>Value</key>
+            <string>00000000-0000-0000-0000-000000000000</string>
+    </map>
 
     <!-- Settings below are for back compatibility only.
     They are not used in current viewer anymore. But they can't be removed to avoid
diff --git a/indra/newview/llcallfloater.cpp b/indra/newview/llcallfloater.cpp
index dd99c6564c15aacc9ee44d6fdb4b6517708a94be..60a2392d87d6d33be554bf3ff59c084ef8ff8ec2 100644
--- a/indra/newview/llcallfloater.cpp
+++ b/indra/newview/llcallfloater.cpp
@@ -95,7 +95,7 @@ static void* create_non_avatar_caller(void*)
 	return new LLNonAvatarCaller;
 }
 
-LLVoiceChannel* LLCallFloater::sCurrentVoiceCanel = NULL;
+LLVoiceChannel* LLCallFloater::sCurrentVoiceChannel = NULL;
 
 LLCallFloater::LLCallFloater(const LLSD& key)
 : LLTransientDockableFloater(NULL, false, key)
@@ -113,7 +113,7 @@ LLCallFloater::LLCallFloater(const LLSD& key)
 	mSpeakerDelayRemover = new LLSpeakersDelayActionsStorage(boost::bind(&LLCallFloater::removeVoiceLeftParticipant, this, _1), voice_left_remove_delay);
 
 	mFactoryMap["non_avatar_caller"] = LLCallbackMap(create_non_avatar_caller, NULL);
-	LLVoiceClient::getInstance()->addObserver(this);
+	LLVoiceClient::instance().addObserver(this);
 	LLTransientFloaterMgr::getInstance()->addControlView(this);
 
 	// force docked state since this floater doesn't save it between recreations
@@ -158,7 +158,6 @@ BOOL LLCallFloater::postBuild()
 
 	initAgentData();
 
-
 	connectToChannel(LLVoiceChannel::getCurrentVoiceChannel());
 
 	setIsChrome(true);
@@ -204,7 +203,7 @@ void LLCallFloater::draw()
 }
 
 // virtual
-void LLCallFloater::onChange()
+void LLCallFloater::onParticipantsChanged()
 {
 	if (NULL == mParticipants) return;
 	updateParticipantsVoiceState();
@@ -287,22 +286,22 @@ void LLCallFloater::updateSession()
 
 	if (NULL == mSpeakerManager)
 	{
-		// by default let show nearby chat participants
+		// By default show nearby chat participants
 		mSpeakerManager = LLLocalSpeakerMgr::getInstance();
 		LL_DEBUGS("Voice") << "Set DEFAULT speaker manager" << LL_ENDL;
 		mVoiceType = VC_LOCAL_CHAT;
 	}
 
 	updateTitle();
-	
-	//hide "Leave Call" button for nearby chat
+
+	// Hide "Leave Call" button for nearby chat
 	bool is_local_chat = mVoiceType == VC_LOCAL_CHAT;
 	childSetVisible("leave_call_btn_panel", !is_local_chat);
 
 	refreshParticipantList();
 	updateAgentModeratorState();
 
-	//show floater for voice calls & only in CONNECTED to voice channel state
+	// Show floater for voice calls & only in CONNECTED to voice channel state
 	if (!is_local_chat &&
 	    voice_channel &&
 	    LLVoiceChannel::STATE_CONNECTED == voice_channel->getState())
@@ -368,7 +367,7 @@ void LLCallFloater::sOnCurrentChannelChanged(const LLUUID& /*session_id*/)
 	// *NOTE: if signal was sent for voice channel with LLVoiceChannel::STATE_NO_CHANNEL_INFO
 	// it sill be sent for the same channel again (when state is changed).
 	// So, lets ignore this call.
-	if (channel == sCurrentVoiceCanel) return;
+	if (channel == sCurrentVoiceChannel) return;
 
 	LLCallFloater* call_floater = LLFloaterReg::getTypedInstance<LLCallFloater>("voice_controls");
 
@@ -715,9 +714,9 @@ void LLCallFloater::connectToChannel(LLVoiceChannel* channel)
 {
 	mVoiceChannelStateChangeConnection.disconnect();
 
-	sCurrentVoiceCanel = channel;
+	sCurrentVoiceChannel = channel;
 
-	mVoiceChannelStateChangeConnection = sCurrentVoiceCanel->setStateChangedCallback(boost::bind(&LLCallFloater::onVoiceChannelStateChanged, this, _1, _2));
+	mVoiceChannelStateChangeConnection = sCurrentVoiceChannel->setStateChangedCallback(boost::bind(&LLCallFloater::onVoiceChannelStateChanged, this, _1, _2));
 
 	updateState(channel->getState());
 }
@@ -737,7 +736,7 @@ void LLCallFloater::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old
 
 void LLCallFloater::updateState(const LLVoiceChannel::EState& new_state)
 {
-	LL_DEBUGS("Voice") << "Updating state: " << new_state << ", session name: " << sCurrentVoiceCanel->getSessionName() << LL_ENDL;
+	LL_DEBUGS("Voice") << "Updating state: " << new_state << ", session name: " << sCurrentVoiceChannel->getSessionName() << LL_ENDL;
 	if (LLVoiceChannel::STATE_CONNECTED == new_state)
 	{
 		updateSession();
diff --git a/indra/newview/llcallfloater.h b/indra/newview/llcallfloater.h
index 0a8ea7de39995e879e2246090aac810bc5f1f2a1..e4341175e28eded36ae6c6d4df0e7f1d7a0644cc 100644
--- a/indra/newview/llcallfloater.h
+++ b/indra/newview/llcallfloater.h
@@ -47,15 +47,15 @@ class LLSpeakerMgr;
 class LLSpeakersDelayActionsStorage;
 
 /**
- * The Voice Control Panel is an ambient window summoned by clicking the flyout chevron on the Speak button.
- * It can be torn-off and freely positioned onscreen.
+ * The Voice Control Panel is an ambient window summoned by clicking the flyout chevron
+ * on the Speak button. It can be torn-off and freely positioned onscreen.
  *
- * When the Resident is engaged in Nearby Voice Chat, the Voice Control Panel provides control over 
- * the Resident's own microphone input volume, the audible volume of each of the other participants,
- * the Resident's own Voice Morphing settings (if she has subscribed to enable the feature), and Voice Recording.
+ * When the Resident is engaged in Voice Chat, the Voice Control Panel provides control
+ * over the audible volume of each of the other participants, the Resident's own Voice
+ * Morphing settings (if she has subscribed to enable the feature), and Voice Recording.
  *
- * When the Resident is engaged in any chat except Nearby Chat, the Voice Control Panel also provides an 
- * 'Leave Call' button to allow the Resident to leave that voice channel.
+ * When the Resident is engaged in any chat except Nearby Chat, the Voice Control Panel
+ * also provides a 'Leave Call' button to allow the Resident to leave that voice channel.
  */
 class LLCallFloater : public LLTransientDockableFloater, LLVoiceClientParticipantObserver
 {
@@ -75,7 +75,7 @@ public:
 	 *
 	 * Refreshes list to display participants not in voice as disabled.
 	 */
-	/*virtual*/ void onChange();
+	/*virtual*/ void onParticipantsChanged();
 
 	static void sOnCurrentChannelChanged(const LLUUID& session_id);
 
@@ -259,7 +259,7 @@ private:
 	 *
 	 * @see sOnCurrentChannelChanged()
 	 */
-	static LLVoiceChannel* sCurrentVoiceCanel;
+	static LLVoiceChannel* sCurrentVoiceChannel;
 
 	/* virtual */
 	LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::IM; }
diff --git a/indra/newview/llfloaternotificationsconsole.cpp b/indra/newview/llfloaternotificationsconsole.cpp
index b744bff084012e3d3ab0420010b201b0ca591135..105d7f92015abd2a18c53f02c08df1468e1e8ecc 100644
--- a/indra/newview/llfloaternotificationsconsole.cpp
+++ b/indra/newview/llfloaternotificationsconsole.cpp
@@ -184,7 +184,7 @@ BOOL LLFloaterNotificationConsole::postBuild()
 	addChannel("Ignore");
 	addChannel("Visible", true);
 	// all the ones below attach to the Visible channel
-	addChannel("History");
+	addChannel("Persistent");
 	addChannel("Alerts");
 	addChannel("AlertModal");
 	addChannel("Group Notifications");
diff --git a/indra/newview/llfloatervoiceeffect.cpp b/indra/newview/llfloatervoiceeffect.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f31ab969853d9d1932ab33edebfe3ed167fb3baa
--- /dev/null
+++ b/indra/newview/llfloatervoiceeffect.cpp
@@ -0,0 +1,296 @@
+/** 
+ * @file llfloatervoiceeffect.cpp
+ * @brief Selection and preview of voice effect.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ * 
+ * Copyright (c) 2002-2009, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llfloatervoiceeffect.h"
+
+#include "llscrolllistctrl.h"
+#include "lltrans.h"
+#include "llweb.h"
+
+LLFloaterVoiceEffect::LLFloaterVoiceEffect(const LLSD& key)
+	: LLFloater(key)
+{
+	mCommitCallbackRegistrar.add("VoiceEffect.Record",	boost::bind(&LLFloaterVoiceEffect::onClickRecord, this));
+	mCommitCallbackRegistrar.add("VoiceEffect.Play",	boost::bind(&LLFloaterVoiceEffect::onClickPlay, this));
+	mCommitCallbackRegistrar.add("VoiceEffect.Stop",	boost::bind(&LLFloaterVoiceEffect::onClickStop, this));
+	mCommitCallbackRegistrar.add("VoiceEffect.Add",		boost::bind(&LLFloaterVoiceEffect::onClickAdd, this));
+//	mCommitCallbackRegistrar.add("VoiceEffect.Activate", boost::bind(&LLFloaterVoiceEffect::onClickActivate, this));
+}
+
+// virtual
+LLFloaterVoiceEffect::~LLFloaterVoiceEffect()
+{
+	if(LLVoiceClient::instanceExists())
+	{
+		LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+		if (effect_interface)
+		{
+			effect_interface->removeObserver(this);
+		}
+	}
+}
+
+// virtual
+BOOL LLFloaterVoiceEffect::postBuild()
+{
+	setDefaultBtn("record_btn");
+	getChild<LLButton>("record_btn")->setFocus(true);
+
+
+	mVoiceEffectList = getChild<LLScrollListCtrl>("voice_effect_list");
+	if (mVoiceEffectList)
+	{
+		mVoiceEffectList->setCommitCallback(boost::bind(&LLFloaterVoiceEffect::onClickPlay, this));
+//		mVoiceEffectList->setDoubleClickCallback(boost::bind(&LLFloaterVoiceEffect::onClickActivate, this));
+	}
+
+	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+	if (effect_interface)
+	{
+		effect_interface->addObserver(this);
+
+		// Disconnect from the current voice channel ready to record a voice sample for previewing
+		effect_interface->enablePreviewBuffer(true);
+	}
+
+	refreshEffectList();
+	updateControls();
+
+	return TRUE;
+}
+
+// virtual
+void LLFloaterVoiceEffect::onClose(bool app_quitting)
+{
+	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+	if (effect_interface)
+	{
+		effect_interface->enablePreviewBuffer(false);
+	}
+}
+
+void LLFloaterVoiceEffect::refreshEffectList()
+{
+	if (!mVoiceEffectList)
+	{
+		return;
+	}
+
+	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+	if (!effect_interface)
+	{
+		mVoiceEffectList->setEnabled(false);
+		return;
+	}
+
+	LL_DEBUGS("Voice")<< "Rebuilding voice effect list."<< LL_ENDL;
+
+	// Preserve selected items and scroll position
+	S32 scroll_pos = mVoiceEffectList->getScrollPos();
+	uuid_vec_t selected_items;
+	std::vector<LLScrollListItem*> items = mVoiceEffectList->getAllSelected();
+	for(std::vector<LLScrollListItem*>::const_iterator it = items.begin(); it != items.end(); it++)
+	{
+		selected_items.push_back((*it)->getUUID());
+	}
+
+	mVoiceEffectList->deleteAllItems();
+
+	{
+		// Add the "No Voice Effect" entry
+		LLSD element;
+
+		element["id"] = LLUUID::null;
+		element["columns"][0]["column"] = "name";
+		element["columns"][0]["value"] = getString("no_voice_effect");
+		element["columns"][0]["font"]["name"] = "SANSSERIF";
+		element["columns"][0]["font"]["style"] = "ITALIC";
+
+		LLScrollListItem* sl_item = mVoiceEffectList->addElement(element, ADD_BOTTOM);
+		// *HACK: Copied from llfloatergesture.cpp : ["font"]["style"] does not affect font style :(
+		if(sl_item)
+		{
+			((LLScrollListText*)sl_item->getColumn(0))->setFontStyle(LLFontGL::BOLD);
+		}
+	}
+
+	const voice_effect_list_t& template_list = effect_interface->getVoiceEffectTemplateList();
+	if (!template_list.empty())
+	{
+		for (voice_effect_list_t::const_iterator it = template_list.begin(); it != template_list.end(); ++it)
+		{
+			const LLUUID& effect_id = it->second;
+			std::string effect_name = it->first;
+
+			LLSD effect_properties = effect_interface->getVoiceEffectProperties(effect_id);
+
+			// Tag the active effect.
+			if (effect_id == LLVoiceClient::instance().getVoiceEffectDefault())
+			{
+				effect_name += " " + getString("active_voice_effect");
+			}
+
+			LLDate expiry_date = effect_properties["expiry_date"].asDate();
+			bool is_template_only = effect_properties["template_only"].asBoolean();
+			bool is_new = effect_properties["is_new"].asBoolean();
+
+			std::string font_style = "NORMAL";
+			if (!is_template_only)
+			{
+				font_style = "BOLD";
+			}
+			LLSD element;
+			element["id"] = effect_id;
+
+			element["columns"][0]["column"] = "name";
+			element["columns"][0]["value"] = effect_name;
+			element["columns"][0]["font"]["name"] = "SANSSERIF";
+			element["columns"][0]["font"]["style"] = font_style;
+
+			element["columns"][1]["column"] = "new";
+			element["columns"][1]["value"] = is_new ? getString("new_voice_effect") : "";
+			element["columns"][1]["font"]["name"] = "SANSSERIF";
+			element["columns"][1]["font"]["style"] = font_style;
+
+			element["columns"][2]["column"] = "expires";
+			if (!is_template_only)
+			{
+				element["columns"][2]["value"] = expiry_date;
+			}
+			else {
+				element["columns"][2]["value"] = "";
+			}
+			element["columns"][2]["font"]["name"] = "SANSSERIF";
+			element["columns"][2]["font"]["style"] = font_style;
+
+			LLScrollListItem* sl_item = mVoiceEffectList->addElement(element, ADD_BOTTOM);
+			// *HACK: Copied from llfloatergesture.cpp : ["font"]["style"] does not affect font style :(
+			if(sl_item)
+			{
+				LLFontGL::StyleFlags style = is_template_only ? LLFontGL::NORMAL : LLFontGL::BOLD;
+				((LLScrollListText*)sl_item->getColumn(0))->setFontStyle(style);
+			}
+		}
+	}
+
+	// Re-select items that were selected before, and restore the scroll position
+	for(uuid_vec_t::iterator it = selected_items.begin(); it != selected_items.end(); it++)
+	{
+		mVoiceEffectList->selectByID(*it);
+	}
+	mVoiceEffectList->setScrollPos(scroll_pos);
+	mVoiceEffectList->setEnabled(true);
+}
+
+void LLFloaterVoiceEffect::updateControls()
+{
+	bool recording = false;
+	bool playing = false;
+
+	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+	if (effect_interface)
+	{
+		recording = effect_interface->isPreviewRecording();
+		playing = effect_interface->isPreviewPlaying();
+	}
+
+	getChild<LLButton>("record_btn")->setVisible(!recording);
+	getChild<LLButton>("record_stop_btn")->setVisible(recording);
+}
+
+// virtual
+void LLFloaterVoiceEffect::onVoiceEffectChanged(bool effect_list_updated)
+{
+	if (effect_list_updated)
+	{
+		refreshEffectList();
+	}
+	updateControls();
+}
+
+void LLFloaterVoiceEffect::onClickRecord()
+{
+	LL_DEBUGS("Voice") << "Record clicked" << LL_ENDL;
+	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+	if (effect_interface)
+	{
+		effect_interface->recordPreviewBuffer();
+	}
+	updateControls();
+}
+
+void LLFloaterVoiceEffect::onClickPlay()
+{
+	LL_DEBUGS("Voice") << "Play clicked" << LL_ENDL;
+	if (!mVoiceEffectList)
+	{
+		return;
+	}
+
+	const LLUUID& effect_id = mVoiceEffectList->getCurrentID();
+
+	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+	if (effect_interface)
+	{
+		effect_interface->playPreviewBuffer(effect_id);
+	}
+	updateControls();
+}
+
+void LLFloaterVoiceEffect::onClickStop()
+{
+	LL_DEBUGS("Voice") << "Stop clicked" << LL_ENDL;
+	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+	if (effect_interface)
+	{
+		effect_interface->stopPreviewBuffer();
+	}
+	updateControls();
+}
+
+void LLFloaterVoiceEffect::onClickAdd()
+{
+	// Open the voice morphing info web page
+	LLWeb::loadURL(LLTrans::getString("voice_morphing_url"));
+}
+
+//void LLFloaterVoiceEffect::onClickActivate()
+//{
+//	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+//	if (effect_interface && mVoiceEffectList)
+//	{
+//		effect_interface->setVoiceEffect(mVoiceEffectList->getCurrentID());
+//	}
+//}
+
diff --git a/indra/newview/llfloatervoiceeffect.h b/indra/newview/llfloatervoiceeffect.h
new file mode 100644
index 0000000000000000000000000000000000000000..46b241bd172752a8e2f1c3adeb98c6422f56af77
--- /dev/null
+++ b/indra/newview/llfloatervoiceeffect.h
@@ -0,0 +1,72 @@
+/** 
+ * @file llfloatervoiceeffect.h
+ * @brief Selection and preview of voice effects.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ * 
+ * Copyright (c) 2002-2009, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLFLOATERVOICEEFFECT_H
+#define LL_LLFLOATERVOICEEFFECT_H
+
+#include "llfloater.h"
+#include "llvoiceclient.h"
+
+class LLButton;
+class LLScrollListCtrl;
+
+class LLFloaterVoiceEffect
+	: public LLFloater
+	, public LLVoiceEffectObserver
+{
+public:
+	LOG_CLASS(LLFloaterVoiceEffect);
+
+	LLFloaterVoiceEffect(const LLSD& key);
+	virtual ~LLFloaterVoiceEffect();
+
+	virtual BOOL postBuild();
+	virtual void onClose(bool app_quitting);
+
+private:
+	void refreshEffectList();
+	void updateControls();
+
+	/// Called by voice effect provider when voice effect list is changed.
+	virtual void onVoiceEffectChanged(bool effect_list_updated);
+
+	void onClickRecord();
+	void onClickPlay();
+	void onClickStop();
+ 	void onClickAdd();
+// 	void onClickActivate();
+
+	LLUUID mSelectedID;
+	LLScrollListCtrl* mVoiceEffectList;
+};
+
+#endif
diff --git a/indra/newview/llpanelvoiceeffect.cpp b/indra/newview/llpanelvoiceeffect.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4f73f38edcd2886f52d33a13d4317f0291415655
--- /dev/null
+++ b/indra/newview/llpanelvoiceeffect.cpp
@@ -0,0 +1,150 @@
+/** 
+ * @file llpanelvoiceeffect.cpp
+ * @author Aimee Walton
+ * @brief Panel to select Voice Effects.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ * 
+ * Copyright (c) 2010, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llpanelvoiceeffect.h"
+
+#include "llcombobox.h"
+#include "llfloaterreg.h"
+#include "llpanel.h"
+#include "lltrans.h"
+#include "llvoiceclient.h"
+
+static LLRegisterPanelClassWrapper<LLPanelVoiceEffect> t_panel_voice_effect("panel_voice_effect");
+
+LLPanelVoiceEffect::LLPanelVoiceEffect()
+	: mVoiceEffectCombo(NULL)
+{
+	mCommitCallbackRegistrar.add("Voice.CommitVoiceEffect", boost::bind(&LLPanelVoiceEffect::onCommitVoiceEffect, this));
+}
+
+LLPanelVoiceEffect::~LLPanelVoiceEffect()
+{
+	if(LLVoiceClient::instanceExists())
+	{
+		LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+		if (effect_interface)
+		{
+			effect_interface->removeObserver(this);
+		}
+	}
+}
+
+// virtual
+BOOL LLPanelVoiceEffect::postBuild()
+{
+	mVoiceEffectCombo = getChild<LLComboBox>("voice_effect");
+
+	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+	if (effect_interface)
+	{
+		effect_interface->addObserver(this);
+	}
+
+	update();
+
+	return TRUE;
+}
+
+//////////////////////////////////////////////////////////////////////////
+/// PRIVATE SECTION
+//////////////////////////////////////////////////////////////////////////
+
+void LLPanelVoiceEffect::onCommitVoiceEffect()
+{
+	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+	if (!effect_interface)
+	{
+		mVoiceEffectCombo->setEnabled(false);
+		return;
+	}
+
+	LLSD value = mVoiceEffectCombo->getValue();
+	if (value.asInteger() == PREVIEW_VOICE_EFFECTS)
+	{
+		// Open the voice effects management floater
+		LLFloaterReg::showInstance("voice_effect");
+	}
+	else if (value.asInteger() == GET_VOICE_EFFECTS)
+	{
+		// Open the voice morphing info web page
+		LLWeb::loadURL(LLTrans::getString("voice_morphing_url"));
+	}
+	else
+	{
+		effect_interface->setVoiceEffect(value.asUUID());
+	}
+
+	mVoiceEffectCombo->setValue(effect_interface->getVoiceEffect());
+}
+
+// virtual
+void LLPanelVoiceEffect::onVoiceEffectChanged(bool effect_list_updated)
+{
+	update();
+}
+
+void LLPanelVoiceEffect::update()
+{
+	if (mVoiceEffectCombo)
+	{
+		LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+		if (!effect_interface || !LLVoiceClient::instance().isVoiceWorking())
+		{
+			mVoiceEffectCombo->setEnabled(false);
+			return;
+		}
+
+		mVoiceEffectCombo->removeall();
+		mVoiceEffectCombo->add(getString("no_voice_effect"), LLUUID::null);
+		mVoiceEffectCombo->addSeparator();
+
+		const voice_effect_list_t& effect_list = effect_interface->getVoiceEffectList();
+		if (!effect_list.empty())
+		{
+			for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it)
+			{
+				mVoiceEffectCombo->add(it->first, it->second, ADD_BOTTOM);
+			}
+
+			mVoiceEffectCombo->addSeparator();
+		}
+
+		mVoiceEffectCombo->add(getString("preview_voice_effects"), PREVIEW_VOICE_EFFECTS);
+		mVoiceEffectCombo->add(getString("get_voice_effects"), GET_VOICE_EFFECTS);
+
+		mVoiceEffectCombo->setValue(effect_interface->getVoiceEffect());
+		mVoiceEffectCombo->setEnabled(true);
+	}
+}
diff --git a/indra/newview/llpanelvoiceeffect.h b/indra/newview/llpanelvoiceeffect.h
new file mode 100644
index 0000000000000000000000000000000000000000..bd7bdd04f21d2fdf05201bbf789002e34af6c119
--- /dev/null
+++ b/indra/newview/llpanelvoiceeffect.h
@@ -0,0 +1,73 @@
+/** 
+ * @file llpanelvoiceeffect.h
+ * @author Aimee Walton
+ * @brief Panel to select Voice Effects.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ * 
+ * Copyright (c) 2010, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_PANELVOICEEFFECT_H
+#define LL_PANELVOICEEFFECT_H
+
+#include "llpanel.h"
+#include "llvoiceclient.h"
+
+class LLComboBox;
+
+class LLPanelVoiceEffect
+	: public LLPanel
+	, public LLVoiceEffectObserver
+{
+public:
+	LOG_CLASS(LLPanelVoiceEffect);
+
+	LLPanelVoiceEffect();
+	virtual ~LLPanelVoiceEffect();
+
+	virtual BOOL postBuild();
+
+private:
+	void onCommitVoiceEffect();
+	void update();
+
+	/// Called by voice effect provider when voice effect list is changed.
+	virtual void onVoiceEffectChanged(bool effect_list_updated);
+
+	// Fixed entries in the voice effect list
+	typedef enum e_voice_effect_combo_items
+	{
+		NO_VOICE_EFFECT = 0,
+		PREVIEW_VOICE_EFFECTS = 1,
+		GET_VOICE_EFFECTS = 2
+	} EVoiceEffectComboItems;
+	
+	LLComboBox* mVoiceEffectCombo;
+};
+
+
+#endif //LL_PANELVOICEEFFECT_H
diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp
index 1117ae05d72dd4a2165d78b16650173b3538b3dc..8839d8426916e53a60a754f1591686b4dfbdf1f2 100644
--- a/indra/newview/llparticipantlist.cpp
+++ b/indra/newview/llparticipantlist.cpp
@@ -94,7 +94,7 @@ public:
 		mAvalineCallers.insert(avaline_caller_id);
 	}
 
-	void onChange()
+	void onParticipantsChanged()
 	{
 		uuid_set_t participant_uuids;
 		LLVoiceClient::getInstance()->getParticipantList(participant_uuids);
diff --git a/indra/newview/llspeakingindicatormanager.cpp b/indra/newview/llspeakingindicatormanager.cpp
index 29237946d2b7dc63525f1a2dd2664b7d26764e3c..ea7601517d95f3d25c7442e6e3ce0c87c1fcb544 100644
--- a/indra/newview/llspeakingindicatormanager.cpp
+++ b/indra/newview/llspeakingindicatormanager.cpp
@@ -107,7 +107,7 @@ private:
 	 * So, method does not calculate difference between these list it only switches off already 
 	 * switched on indicators and switches on indicators of voice channel participants
 	 */
-	void onChange();
+	void onParticipantsChanged();
 
 	/**
 	 * Changes state of indicators specified by LLUUIDs
@@ -205,7 +205,7 @@ void SpeakingIndicatorManager::sOnCurrentChannelChanged(const LLUUID& /*session_
 	mSwitchedIndicatorsOn.clear();
 }
 
-void SpeakingIndicatorManager::onChange()
+void SpeakingIndicatorManager::onParticipantsChanged()
 {
 	LL_DEBUGS("SpeakingIndicator") << "Voice participant list was changed, updating indicators" << LL_ENDL;
 
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 49ea0348f9e951b485babb1d9adc9dcb0a574c51..efe59744bca731e5b88e835a9c09186e502851e5 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -103,6 +103,7 @@
 #include "llfloateruipreview.h"
 #include "llfloaterurldisplay.h"
 #include "llfloatervoicedevicesettings.h"
+#include "llfloatervoiceeffect.h"
 #include "llfloaterwater.h"
 #include "llfloaterwhitelistentry.h"
 #include "llfloaterwindlight.h"
@@ -255,7 +256,8 @@ void LLViewerFloaterReg::registerFloaters()
 	LLFloaterReg::add("upload_sound", "floater_sound_preview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSoundPreview>, "upload");
 	
 	LLFloaterReg::add("voice_controls", "floater_voice_controls.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLCallFloater>);
-	
+	LLFloaterReg::add("voice_effect", "floater_voice_effect.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterVoiceEffect>);
+
 	LLFloaterReg::add("whitelist_entry", "floater_whitelist_entry.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterWhiteListEntry>);	
 	LLFloaterWindowSizeUtil::registerFloater();
 	LLFloaterReg::add("world_map", "floater_world_map.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterWorldMap>);	
diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp
index 1b4471a9fe3a5469dd249db0f74c0b095802e81d..070663e22f94163397bc701ae9f195c116baa28a 100644
--- a/indra/newview/llvoicechannel.cpp
+++ b/indra/newview/llvoicechannel.cpp
@@ -897,9 +897,9 @@ void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::s
 	else
 	{
 		LL_WARNS("Voice") << "incoming SIP URL is not provided. Channel may not work properly." << LL_ENDL;
-		// In case of incoming AvaLine call generated URI will be differ from original one.
-		// This is because Avatar-2-Avatar URI is based on avatar UUID but Avaline is not.
-		// See LLVoiceClient::sessionAddedEvent() -> setUUIDFromStringHash()
+		// In the case of an incoming AvaLine call, the generated URI will be different from the
+		// original one. This is because the P2P URI is based on avatar UUID but Avaline is not.
+		// See LLVoiceClient::sessionAddedEvent()
 		setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID));
 	}
 	
diff --git a/indra/newview/llvoicechannel.h b/indra/newview/llvoicechannel.h
index 573fab1f4f645bb51fb93a5a85de36f1fcf7ef82..074f9b8bba58259f10771bc15466695f5e796c19 100644
--- a/indra/newview/llvoicechannel.h
+++ b/indra/newview/llvoicechannel.h
@@ -113,7 +113,7 @@ protected:
 	void doSetState(const EState& state);
 	void setURI(std::string uri);
 
-	// there can be two directions ICOMING and OUTGOING
+	// there can be two directions INCOMING and OUTGOING
 	EDirection mCallDirection;
 
 	std::string	mURI;
diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp
index 91353281a86719ba44e1eacb84f8e8bd7d897f81..3a51f51c5209eb39663f57762b9745be4e344e41 100644
--- a/indra/newview/llvoiceclient.cpp
+++ b/indra/newview/llvoiceclient.cpp
@@ -35,6 +35,7 @@
 #include "llviewerwindow.h"
 #include "llvoicevivox.h"
 #include "llviewernetwork.h"
+#include "llcommandhandler.h"
 #include "llhttpnode.h"
 #include "llnotificationsutil.h"
 #include "llsdserialize.h"
@@ -46,6 +47,39 @@ const F32 LLVoiceClient::VOLUME_MIN = 0.f;
 const F32 LLVoiceClient::VOLUME_DEFAULT = 0.5f;
 const F32 LLVoiceClient::VOLUME_MAX = 1.0f;
 
+
+// Support for secondlife:///app/voice SLapps
+class LLVoiceHandler : public LLCommandHandler
+{
+public:
+	// requests will be throttled from a non-trusted browser
+	LLVoiceHandler() : LLCommandHandler("voice", UNTRUSTED_THROTTLE) {}
+
+	bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web)
+	{
+		if (params[0].asString() == "effects")
+		{
+			LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+			// If the voice client doesn't support voice effects, we can't handle effects SLapps
+			if (!effect_interface)
+			{
+				return false;
+			}
+
+			// Support secondlife:///app/voice/effects/refresh to update the voice effect list with new effects
+			if (params[1].asString() == "refresh")
+			{
+				effect_interface->refreshVoiceEffectLists(false);
+				return true;
+			}
+		}
+		return false;
+	}
+};
+LLVoiceHandler gVoiceHandler;
+
+
+
 std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus)
 {
 	std::string result = "UNKNOWN";
@@ -77,12 +111,13 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv
 
 
 
-
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-LLVoiceClient::LLVoiceClient()
+LLVoiceClient::LLVoiceClient() :
+	mVoiceModule(NULL),
+	mVoiceEffectEnabled(LLCachedControl<bool>(gSavedSettings, "VoiceMorphingEnabled")),
+	mVoiceEffectDefault(LLCachedControl<std::string>(gSavedPerAccountSettings, "VoiceEffectDefault"))
 {
-	mVoiceModule = NULL;
 }
 
 //---------------------------------------------------
@@ -565,7 +600,7 @@ std::string LLVoiceClient::getDisplayName(const LLUUID& id)
 	}
 }
 
-bool LLVoiceClient::isVoiceWorking()
+bool LLVoiceClient::isVoiceWorking() const
 {
 	if (mVoiceModule) 
 	{
@@ -708,6 +743,10 @@ std::string LLVoiceClient::sipURIFromID(const LLUUID &id)
 	}
 }
 
+LLVoiceEffectInterface* LLVoiceClient::getVoiceEffectInterface() const
+{
+	return getVoiceEffectEnabled() ? dynamic_cast<LLVoiceEffectInterface*>(mVoiceModule) : NULL;
+}
 
 ///////////////////
 // version checking
diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h
index e08fed7ae9d53dc5c9fc78bc01feb4b37229eeee..744ec8402323238501b2dcf7f458f2d55263a86a 100644
--- a/indra/newview/llvoiceclient.h
+++ b/indra/newview/llvoiceclient.h
@@ -42,6 +42,7 @@ class LLVOAvatar;
 #include "llviewerregion.h"
 #include "llcallingcard.h"   // for LLFriendObserver
 #include "llsecapi.h"
+#include "llcontrol.h"
 
 // devices
 
@@ -52,7 +53,7 @@ class LLVoiceClientParticipantObserver
 {
 public:
 	virtual ~LLVoiceClientParticipantObserver() { }
-	virtual void onChange() = 0;
+	virtual void onParticipantsChanged() = 0;
 };
 
 
@@ -109,7 +110,7 @@ public:
 	
 	virtual void updateSettings()=0; // call after loading settings and whenever they change
 	
-	virtual bool isVoiceWorking()=0; // connected to a voice server and voice channel
+	virtual bool isVoiceWorking() const = 0; // connected to a voice server and voice channel
 
 	virtual const LLVoiceVersionInfo& getVersion()=0;
 	
@@ -217,8 +218,6 @@ public:
 	//////////////////////////
 	/// @name nearby speaker accessors
 	//@{
-
-
 	virtual BOOL getVoiceEnabled(const LLUUID& id)=0;		// true if we've received data for this avatar
 	virtual std::string getDisplayName(const LLUUID& id)=0;
 	virtual BOOL isOnlineSIP(const LLUUID &id)=0;	
@@ -261,6 +260,63 @@ public:
 };
 
 
+//////////////////////////////////
+/// @class LLVoiceEffectObserver
+class LLVoiceEffectObserver
+{
+public:
+	virtual ~LLVoiceEffectObserver() { }
+	virtual void onVoiceEffectChanged(bool effect_list_updated) = 0;
+};
+
+typedef std::multimap<const std::string, const LLUUID, LLDictionaryLess> voice_effect_list_t;
+
+//////////////////////////////////
+/// @class LLVoiceEffectInterface
+/// @brief Voice effect module interface
+///
+/// Voice effect modules should provide an implementation for this interface.
+/////////////////////////////////
+
+class LLVoiceEffectInterface
+{
+public:
+	LLVoiceEffectInterface() {}
+	virtual ~LLVoiceEffectInterface() {}
+
+	//////////////////////////
+	/// @name Accessors
+	//@{
+	virtual bool setVoiceEffect(const LLUUID& id) = 0;
+	virtual const LLUUID getVoiceEffect() = 0;
+	virtual LLSD getVoiceEffectProperties(const LLUUID& id) = 0;
+
+	virtual void refreshVoiceEffectLists(bool clear_lists) = 0;
+	virtual const voice_effect_list_t &getVoiceEffectList() const = 0;
+	virtual const voice_effect_list_t &getVoiceEffectTemplateList() const = 0;
+	//@}
+
+	//////////////////////////////
+	/// @name Status notification
+	//@{
+	virtual void addObserver(LLVoiceEffectObserver* observer) = 0;
+	virtual void removeObserver(LLVoiceEffectObserver* observer) = 0;
+	//@}
+
+	//////////////////////////////
+	/// @name Preview buffer
+	//@{
+	virtual void enablePreviewBuffer(bool enable) = 0;
+	virtual void recordPreviewBuffer() = 0;
+	virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) = 0;
+	virtual void stopPreviewBuffer() = 0;
+
+	virtual bool isPreviewRecording() = 0;
+	virtual bool isPreviewPlaying() = 0;
+	//@}
+};
+
+
 class LLVoiceClient: public LLSingleton<LLVoiceClient>
 {
 	LOG_CLASS(LLVoiceClient);
@@ -281,7 +337,7 @@ public:
 
 	void updateSettings(); // call after loading settings and whenever they change
 
-	bool isVoiceWorking(); // connected to a voice server and voice channel
+	bool isVoiceWorking() const; // connected to a voice server and voice channel
 
 	// tuning
 	void tuningStart();
@@ -403,10 +459,23 @@ public:
 	void removeObserver(LLVoiceClientParticipantObserver* observer);
 	
 	std::string sipURIFromID(const LLUUID &id);	
-		
+
+	//////////////////////////
+	/// @name Voice effects
+	//@{
+	bool getVoiceEffectEnabled() const { return mVoiceEffectEnabled; };
+	LLUUID getVoiceEffectDefault() const { return LLUUID(mVoiceEffectDefault); };
+	
+	// Returns NULL if voice effects are not supported, or not enabled.
+	LLVoiceEffectInterface* getVoiceEffectInterface() const;
+	//@}
+
 protected:
 	LLVoiceModuleInterface* mVoiceModule;
 	LLPumpIO *m_servicePump;
+
+	LLCachedControl<bool> mVoiceEffectEnabled;
+	LLCachedControl<std::string> mVoiceEffectDefault;
 };
 
 /**
diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp
index bcb1a70efbe0176bc03a2d833212c8decd3c9e71..c8402af5d1f0c2c8abdc1bfd8d3b17a4e3658192 100644
--- a/indra/newview/llvoicevivox.cpp
+++ b/indra/newview/llvoicevivox.cpp
@@ -60,6 +60,7 @@
 #include "llviewerparcelmgr.h"
 //#include "llfirstuse.h"
 #include "llspeakers.h"
+#include "lltrans.h"
 #include "llviewerwindow.h"
 #include "llviewercamera.h"
 
@@ -67,15 +68,11 @@
 #include "llviewernetwork.h"
 #include "llnotificationsutil.h"
 
+#include "stringize.h"
+
 // for base64 decoding
 #include "apr_base64.h"
 
-// for SHA1 hash
-#include "apr_sha1.h"
-
-// for MD5 hash
-#include "llmd5.h"
-
 #define USE_SESSION_GROUPS 0
 
 const F32 VOLUME_SCALE_VIVOX = 0.01f;
@@ -100,14 +97,12 @@ const int MAX_LOGIN_RETRIES = 12;
 // blocked is VERY rare and it's better to sacrifice response time in this situation for the sake of stability.
 const int MAX_NORMAL_JOINING_SPATIAL_NUM = 50;
 
+// How often to check for expired voice fonts
+const F32 VOICE_FONT_EXPIRY_INTERVAL = 1.f;
+
+// Maximum length of capture buffer recordings
+const F32 CAPTURE_BUFFER_MAX_TIME = 15.f;
 
-static void setUUIDFromStringHash(LLUUID &uuid, const std::string &str)
-{
-	LLMD5 md5_uuid;
-	md5_uuid.update((const unsigned char*)str.data(), str.size());
-	md5_uuid.finalize();
-	md5_uuid.raw_digest(uuid.mData);
-}
 
 static int scale_mic_volume(float volume)
 {
@@ -325,6 +320,7 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() :
 	mBuddyListMapPopulated(false),
 	mBlockRulesListReceived(false),
 	mAutoAcceptRulesListReceived(false),
+
 	mCaptureDeviceDirty(false),
 	mRenderDeviceDirty(false),
 	mSpatialCoordsDirty(false),
@@ -348,10 +344,16 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() :
 	mVoiceEnabled(false),
 	mWriteInProgress(false),
 
-	mLipSyncEnabled(false)
-
+	mLipSyncEnabled(false),
 
+	mVoiceFontsReceived(false),
+	mVoiceFontsNew(false),
 
+	mCaptureBufferMode(false),
+	mCaptureBufferRecording(false),
+	mCaptureBufferRecorded(false),
+	mCaptureBufferPlaying(false),
+	mPlayRequestCount(0)
 {	
 	mSpeakerVolume = scale_speaker_volume(0);
 
@@ -654,6 +656,11 @@ std::string LLVivoxVoiceClient::state2string(LLVivoxVoiceClient::state inState)
 		CASE(stateMicTuningStart);
 		CASE(stateMicTuningRunning);
 		CASE(stateMicTuningStop);
+		CASE(stateCaptureBufferPaused);
+		CASE(stateCaptureBufferRecStart);
+		CASE(stateCaptureBufferRecording);
+		CASE(stateCaptureBufferPlayStart);
+		CASE(stateCaptureBufferPlaying);
 		CASE(stateConnectorStart);
 		CASE(stateConnectorStarting);
 		CASE(stateConnectorStarted);
@@ -662,6 +669,8 @@ std::string LLVivoxVoiceClient::state2string(LLVivoxVoiceClient::state inState)
 		CASE(stateNeedsLogin);
 		CASE(stateLoggingIn);
 		CASE(stateLoggedIn);
+		CASE(stateVoiceFontsWait);
+		CASE(stateVoiceFontsReceived);
 		CASE(stateCreatingSessionGroup);
 		CASE(stateNoChannel);
 		CASE(stateJoiningSession);
@@ -775,8 +784,10 @@ void LLVivoxVoiceClient::stateMachine()
 			// Clean up and reset everything. 
 			closeSocket();
 			deleteAllSessions();
-			deleteAllBuddies();		
-			
+			deleteAllBuddies();
+			deleteAllVoiceFonts();
+			deleteVoiceFontTemplates();
+
 			mConnectorHandle.clear();
 			mAccountHandle.clear();
 			mAccountPassword.clear();
@@ -1126,8 +1137,97 @@ void LLVivoxVoiceClient::stateMachine()
 			
 		}
 		break;
-												
-		//MARK: stateConnectorStart
+
+		//MARK: stateCaptureBufferPaused
+		case stateCaptureBufferPaused:
+			if (!mCaptureBufferMode)
+			{
+				// Leaving capture mode.
+
+				mCaptureBufferRecording = false;
+				mCaptureBufferRecorded = false;
+				mCaptureBufferPlaying = false;
+
+				// Return to stateNoChannel to trigger reconnection to a channel.
+				setState(stateNoChannel);
+			}
+			else if (mCaptureBufferRecording)
+			{
+				setState(stateCaptureBufferRecStart);
+			}
+			else if (mCaptureBufferPlaying)
+			{
+				setState(stateCaptureBufferPlayStart);
+			}
+		break;
+
+		//MARK: stateCaptureBufferRecStart
+		case stateCaptureBufferRecStart:
+			captureBufferRecordStartSendMessage();
+
+			// Flag that something is recorded to allow playback.
+			mCaptureBufferRecorded = true;
+
+			// Start the timer, recording will be stopped when it expires.
+			mCaptureTimer.start();
+			mCaptureTimer.setTimerExpirySec(CAPTURE_BUFFER_MAX_TIME);
+
+			// Update UI, should really use a separate callback.
+			notifyVoiceFontObservers(false);
+
+			setState(stateCaptureBufferRecording);
+		break;
+
+		//MARK: stateCaptureBufferRecording
+		case stateCaptureBufferRecording:
+			if (!mCaptureBufferMode || !mCaptureBufferRecording ||
+				mCaptureBufferPlaying || mCaptureTimer.hasExpired())
+			{
+				// Stop recording
+				captureBufferRecordStopSendMessage();
+				mCaptureBufferRecording = false;
+
+				// Update UI, should really use a separate callback.
+				notifyVoiceFontObservers(false);
+
+				setState(stateCaptureBufferPaused);
+			}
+		break;
+
+		//MARK: stateCaptureBufferPlayStart
+		case stateCaptureBufferPlayStart:
+			captureBufferPlayStartSendMessage(mPreviewVoiceFont);
+
+			// Store the voice font being previewed, so that we know to restart if it changes.
+			mPreviewVoiceFontLast = mPreviewVoiceFont;
+
+			// Update UI, should really use a separate callback.
+			notifyVoiceFontObservers(false);
+
+			setState(stateCaptureBufferPlaying);
+		break;
+
+		//MARK: stateCaptureBufferPlaying
+		case stateCaptureBufferPlaying:
+			if (mCaptureBufferPlaying && mPreviewVoiceFont != mPreviewVoiceFontLast)
+			{
+				// If the preview voice font changes, restart playing with the new font.
+				setState(stateCaptureBufferPlayStart);
+			}
+			else if (!mCaptureBufferMode || !mCaptureBufferPlaying || mCaptureBufferRecording)
+			{
+				// Stop playing.
+				captureBufferPlayStopSendMessage();
+				mCaptureBufferPlaying = false;
+
+				// Update UI, should really use a separate callback.
+				notifyVoiceFontObservers(false);
+
+				setState(stateCaptureBufferPaused);
+			}
+		break;
+
+			//MARK: stateConnectorStart
 		case stateConnectorStart:
 			if(!mVoiceEnabled)
 			{
@@ -1222,6 +1322,17 @@ void LLVivoxVoiceClient::stateMachine()
 
 			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN);
 
+			if (LLVoiceClient::instance().getVoiceEffectEnabled())
+			{
+				// request the set of available voice fonts
+				setState(stateVoiceFontsWait);
+				refreshVoiceEffectLists(true);
+			}
+			else
+			{
+				setState(stateVoiceFontsReceived);
+			}
+
 			// request the current set of block rules (we'll need them when updating the friends list)
 			accountListBlockRulesSendMessage();
 			
@@ -1253,12 +1364,25 @@ void LLVivoxVoiceClient::stateMachine()
 					writeString(stream.str());
 				}
 			}
+		break;
+
+		//MARK: stateVoiceFontsWait
+		case stateVoiceFontsWait:		// Await voice font list
+			// accountGetSessionFontsResponse() will transition from here to
+			// stateVoiceFontsReceived, to ensure we have the voice font list
+			// before attempting to create a session.
+		break;
 			
+		//MARK: stateVoiceFontsReceived
+		case stateVoiceFontsReceived:	// Voice font list received
+			// Set up the timer to check for expiring voice fonts
+			mVoiceFontExpiryTimer.start();
+			mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL);
+
 #if USE_SESSION_GROUPS			
 			// create the main session group
-			sessionGroupCreateSendMessage();
-			
 			setState(stateCreatingSessionGroup);
+			sessionGroupCreateSendMessage();
 #else
 			// Not using session groups -- skip the stateCreatingSessionGroup state.
 			setState(stateNoChannel);
@@ -1306,6 +1430,10 @@ void LLVivoxVoiceClient::stateMachine()
 				mTuningExitState = stateNoChannel;
 				setState(stateMicTuningStart);
 			}
+			else if(mCaptureBufferMode)
+			{
+				setState(stateCaptureBufferPaused);
+			}
 			else if(sessionNeedsRelog(mNextAudioSession))
 			{
 				requestRelog();
@@ -1316,6 +1444,7 @@ void LLVivoxVoiceClient::stateMachine()
 				sessionState *oldSession = mAudioSession;
 
 				mAudioSession = mNextAudioSession;
+				mAudioSessionChanged = true;
 				if(!mAudioSession->mReconnect)	
 				{
 					mNextAudioSession = NULL;
@@ -1478,6 +1607,13 @@ void LLVivoxVoiceClient::stateMachine()
 					enforceTether();
 				}
 				
+				// Do notifications for expiring Voice Fonts.
+				if (mVoiceFontExpiryTimer.hasExpired())
+				{
+					expireVoiceFonts();
+					mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL);
+				}
+
 				// Send an update only if the ptt or mute state has changed (which shouldn't be able to happen that often
 				// -- the user can only click so fast) or every 10hz, whichever is sooner.
 				// Sending for every volume update causes an excessive flood of messages whenever a volume slider is dragged.
@@ -1547,6 +1683,8 @@ void LLVivoxVoiceClient::stateMachine()
 			mAccountHandle.clear();
 			deleteAllSessions();
 			deleteAllBuddies();
+			deleteAllVoiceFonts();
+			deleteVoiceFontTemplates();
 
 			if(mVoiceEnabled && !mRelogRequested)
 			{
@@ -1627,15 +1765,15 @@ void LLVivoxVoiceClient::stateMachine()
 
 	}
 	
-	if(mAudioSession && mAudioSession->mParticipantsChanged)
+	if (mAudioSessionChanged)
 	{
-		mAudioSession->mParticipantsChanged = false;
-		mAudioSessionChanged = true;
+		mAudioSessionChanged = false;
+		notifyParticipantObservers();
+		notifyVoiceFontObservers(false);
 	}
-	
-	if(mAudioSessionChanged)
+	else if (mAudioSession && mAudioSession->mParticipantsChanged)
 	{
-		mAudioSessionChanged = false;
+		mAudioSession->mParticipantsChanged = false;
 		notifyParticipantObservers();
 	}
 }
@@ -1751,8 +1889,11 @@ void LLVivoxVoiceClient::sessionGroupCreateSendMessage()
 
 void LLVivoxVoiceClient::sessionCreateSendMessage(sessionState *session, bool startAudio, bool startText)
 {
-	LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL;
-	
+	LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL;
+
+	S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+	LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL;
+
 	session->mCreateInProgress = true;
 	if(startAudio)
 	{
@@ -1776,10 +1917,11 @@ void LLVivoxVoiceClient::sessionCreateSendMessage(sessionState *session, bool st
 			<< "<Password>" << LLURI::escape(session->mHash, allowed_chars) << "</Password>"
 			<< "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>";
 	}
-	
+
 	stream
 		<< "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
 		<< "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
+		<< "<VoiceFontID>" << font_index << "</VoiceFontID>"
 		<< "<Name>" << mChannelName << "</Name>"
 	<< "</Request>\n\n\n";
 	writeString(stream.str());
@@ -1787,8 +1929,11 @@ void LLVivoxVoiceClient::sessionCreateSendMessage(sessionState *session, bool st
 
 void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio, bool startText)
 {
-	LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL;
-	
+	LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL;
+
+	S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+	LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL;
+
 	session->mCreateInProgress = true;
 	if(startAudio)
 	{
@@ -1814,6 +1959,7 @@ void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session
 		<< "<Name>" << mChannelName << "</Name>"
 		<< "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
 		<< "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
+		<< "<VoiceFontID>" << font_index << "</VoiceFontID>"
 		<< "<Password>" << password << "</Password>"
 		<< "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>"
 	<< "</Request>\n\n\n"
@@ -1824,7 +1970,10 @@ void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session
 
 void LLVivoxVoiceClient::sessionMediaConnectSendMessage(sessionState *session)
 {
-	LL_DEBUGS("Voice") << "connecting audio to session handle: " << session->mHandle << LL_ENDL;
+	LL_DEBUGS("Voice") << "Connecting audio to session handle: " << session->mHandle << LL_ENDL;
+
+	S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+	LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL;
 
 	session->mMediaConnectInProgress = true;
 	
@@ -1834,6 +1983,7 @@ void LLVivoxVoiceClient::sessionMediaConnectSendMessage(sessionState *session)
 	<< "<Request requestId=\"" << session->mHandle << "\" action=\"Session.MediaConnect.1\">"
 		<< "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
 		<< "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+		<< "<VoiceFontID>" << font_index << "</VoiceFontID>"
 		<< "<Media>Audio</Media>"
 	<< "</Request>\n\n\n";
 
@@ -3156,7 +3306,7 @@ void LLVivoxVoiceClient::sessionAddedEvent(
 			else
 			{
 				LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL;
-				setUUIDFromStringHash(session->mCallerID, session->mSIPURI);
+				session->mCallerID.generate(session->mSIPURI);
 				session->mSynthesizedCallerID = true;
 				
 				// Can't look up the name in this case -- we have to extract it from the URI.
@@ -3434,6 +3584,26 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent(
 	}
 }
 
+void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType)
+{
+	if (mediaCompletionType == "AuxBufferAudioCapture")
+	{
+		mCaptureBufferRecording = false;
+	}
+	else if (mediaCompletionType == "AuxBufferAudioRender")
+	{
+		// Ignore all but the last stop event
+		if (--mPlayRequestCount <= 0)
+		{
+			mCaptureBufferPlaying = false;
+		}
+	}
+	else
+	{
+		LL_DEBUGS("Voice") << "Unknown MediaCompletionType: " << mediaCompletionType << LL_ENDL;
+	}
+}
+
 void LLVivoxVoiceClient::mediaStreamUpdatedEvent(
 	std::string &sessionHandle, 
 	std::string &sessionGroupHandle, 
@@ -4134,8 +4304,8 @@ LLVivoxVoiceClient::participantState *LLVivoxVoiceClient::sessionState::addParti
 			else
 			{
 				// Create a UUID by hashing the URI, but do NOT set mAvatarIDValid.
-				// This tells code in LLVivoxVoiceClient that the ID will not be in the name cache.
-				setUUIDFromStringHash(result->mAvatarID, uri);
+				// This indicates that the ID will not be in the name cache.
+				result->mAvatarID.generate(uri);
 			}
 		}
 
@@ -4630,7 +4800,7 @@ BOOL LLVivoxVoiceClient::isOnlineSIP(const LLUUID &id)
 	return result;
 }
 
-bool LLVivoxVoiceClient::isVoiceWorking()
+bool LLVivoxVoiceClient::isVoiceWorking() const
 {
   //Added stateSessionTerminated state to avoid problems with call in parcels with disabled voice (EXT-4758)
   // Condition with joining spatial num was added to take into account possible problems with connection to voice
@@ -5650,7 +5820,12 @@ LLVivoxVoiceClient::sessionState *LLVivoxVoiceClient::addSession(const std::stri
 		result = new sessionState();
 		result->mSIPURI = uri;
 		result->mHandle = handle;
-		
+
+		if (LLVoiceClient::instance().getVoiceEffectEnabled())
+		{
+			result->mVoiceFontID = LLVoiceClient::instance().getVoiceEffectDefault();
+		}
+
 		mSessions.insert(result);
 
 		if(!result->mHandle.empty())
@@ -6075,8 +6250,8 @@ void LLVivoxVoiceClient::notifyParticipantObservers()
 		)
 	{
 		LLVoiceClientParticipantObserver* observer = *it;
-		observer->onChange();
-		// In case onChange() deleted an entry.
+		observer->onParticipantsChanged();
+		// In case onParticipantsChanged() deleted an entry.
 		it = mParticipantObservers.upper_bound(observer);
 	}
 }
@@ -6239,6 +6414,635 @@ void LLVivoxVoiceClient::avatarNameResolved(const LLUUID &id, const std::string
 	}
 }
 
+bool LLVivoxVoiceClient::setVoiceEffect(const LLUUID& id)
+{
+	if (!mAudioSession)
+	{
+		return false;
+	}
+
+	if (!id.isNull())
+	{
+		if (mVoiceFontMap.empty())
+		{
+			LL_DEBUGS("Voice") << "Voice fonts not available." << LL_ENDL;
+			return false;
+		}
+		else if (mVoiceFontMap.find(id) == mVoiceFontMap.end())
+		{
+			LL_DEBUGS("Voice") << "Invalid voice font " << id << LL_ENDL;
+			return false;
+		}
+	}
+
+	// *TODO: Check for expired fonts?
+	mAudioSession->mVoiceFontID = id;
+
+	// *TODO: Separate voice font defaults for spatial chat and IM?
+	gSavedPerAccountSettings.setString("VoiceEffectDefault", id.asString());
+
+	sessionSetVoiceFontSendMessage(mAudioSession);
+	notifyVoiceFontObservers(false);
+
+	return true;
+}
+
+const LLUUID LLVivoxVoiceClient::getVoiceEffect()
+{
+	return mAudioSession ? mAudioSession->mVoiceFontID : LLUUID::null;
+}
+
+LLSD LLVivoxVoiceClient::getVoiceEffectProperties(const LLUUID& id)
+{
+	LLSD sd;
+
+	voice_font_map_t::iterator iter = mVoiceFontMap.find(id);
+	if (iter != mVoiceFontMap.end())
+	{
+		sd["template_only"] = false;
+	}
+	else
+	{
+		// Voice effect is not in the voice font map, see if there is a template
+		iter = mVoiceFontTemplateMap.find(id);
+		if (iter == mVoiceFontTemplateMap.end())
+		{
+			LL_WARNS("Voice") << "Voice effect " << id << "not found." << LL_ENDL;
+			return sd;
+		}
+		sd["template_only"] = true;
+	}
+
+	voiceFontEntry *font = iter->second;
+	sd["name"] = font->mName;
+	sd["expiry_date"] = font->mExpirationDate;
+	sd["is_new"] = font->mIsNew;
+
+	return sd;
+}
+
+LLVivoxVoiceClient::voiceFontEntry::voiceFontEntry(LLUUID& id) :
+	mID(id),
+	mFontIndex(0),
+	mFontType(VOICE_FONT_TYPE_NONE),
+	mFontStatus(VOICE_FONT_STATUS_NONE),
+	mIsNew(false)
+{
+}
+
+LLVivoxVoiceClient::voiceFontEntry::~voiceFontEntry()
+{
+}
+
+void LLVivoxVoiceClient::refreshVoiceEffectLists(bool clear_lists)
+{
+	if (clear_lists)
+	{
+		mVoiceFontsReceived = false;
+		deleteAllVoiceFonts();
+		deleteVoiceFontTemplates();
+	}
+
+	accountGetSessionFontsSendMessage();
+	accountGetTemplateFontsSendMessage();
+}
+
+const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectList() const
+{
+	return mVoiceFontList;
+}
+
+const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectTemplateList() const
+{
+	return mVoiceFontTemplateList;
+}
+
+void LLVivoxVoiceClient::addVoiceFont(const S32 font_index,
+								 const std::string &name,
+								 const std::string &description,
+								 const LLDate &expiration_date,
+								 const bool has_expired,
+								 const S32 font_type,
+								 const S32 font_status,
+								 const bool template_font)
+{
+	// Vivox SessionFontIDs are not guaranteed to remain the same between
+	// sessions or grids so use a UUID for the name.
+
+	// If received name is not a UUID, fudge one by hashing the name and type.
+	LLUUID font_id;
+	if (LLUUID::validate(name))
+	{
+		font_id = LLUUID(name);
+	}
+	else
+	{
+		font_id.generate(STRINGIZE(font_type << ":" << name));
+	}
+
+	voiceFontEntry *font = NULL;
+
+	voice_font_map_t& font_map = template_font ? mVoiceFontTemplateMap : mVoiceFontMap;
+	voice_effect_list_t& font_list = template_font ? mVoiceFontTemplateList : mVoiceFontList;
+
+	// Check whether we've seen this font before.
+	voice_font_map_t::iterator iter = font_map.find(font_id);
+	bool new_font = (iter == font_map.end());
+
+	// If it is a new (unexpired) font create a new entry, otherwise update the existing one.
+	if (new_font)
+	{
+		if (!has_expired)
+		{
+			font = new voiceFontEntry(font_id);
+		}
+		else
+		{
+			LL_DEBUGS("Voice") << (template_font?"Template: ":"") << font_id
+			<< " (" << font_index << ") : " << name << " has expired." << LL_ENDL;
+		}
+
+	}
+	else
+	{
+		font = iter->second;
+	}
+
+	if (font)
+	{
+		// Remove fonts that have expired since we last saw them.
+		if (has_expired)
+		{
+			LL_DEBUGS("Voice") << (template_font?"Template: ":"") << font_id
+			<< " (" << font_index << ") : " << name << " has expired, removing."
+			<< LL_ENDL;
+
+			deleteVoiceFont(font_id);
+			return;
+		}
+
+		font->mFontIndex = font_index;
+		// Use the description for the human readable name if available, as the
+		// "name" may be a UUID.
+		font->mName = description.empty() ? name : description;
+		font->mExpirationDate = expiration_date;
+		font->mFontType = font_type;
+		font->mFontStatus = font_status;
+
+		F64 expiry_time = 0.f;
+
+		// Set the expiry timer to trigger a notification when the voice font can no longer be used.
+		font->mExpiryTimer.start();
+		expiry_time = expiration_date.secondsSinceEpoch() - LLTimer::getTotalSeconds();
+		font->mExpiryTimer.setTimerExpirySec(expiry_time);
+
+		// Set the warning timer to some interval before actual expiry.
+		S32 warning_time = gSavedSettings.getS32("VoiceEffectExpiryWarningTime");
+		if (warning_time > 0)
+		{
+			font->mExpiryWarningTimer.start();
+			expiry_time = (expiration_date.secondsSinceEpoch() - (F64)warning_time)
+							- LLTimer::getTotalSeconds();
+			font->mExpiryWarningTimer.setTimerExpirySec(expiry_time);
+		}
+		else
+		{
+			// Disable the warning timer.
+			font->mExpiryWarningTimer.stop();
+		}
+
+		 // Only flag it as a new font if we have already seen the font list.
+		if (!template_font && mVoiceFontsReceived && new_font)
+		{
+			font->mIsNew = true;
+			mVoiceFontsNew = true;
+		}
+
+		if (new_font)
+		{
+			font_map.insert(voice_font_map_t::value_type(font->mID, font));
+			font_list.insert(voice_effect_list_t::value_type(font->mName, font->mID));
+		}
+
+		// Debugging stuff
+
+		LL_DEBUGS("Voice") << (template_font?"Template: ":"") << font_id
+		<< " (" << font_index << ") : " << name << LL_ENDL;
+
+		if (font_type < VOICE_FONT_TYPE_NONE || font_type >= VOICE_FONT_TYPE_UNKNOWN)
+		{
+			LL_DEBUGS("Voice") << "Unknown voice font type: " << font_type << LL_ENDL;
+		}
+		if (font_status < VOICE_FONT_STATUS_NONE || font_status >= VOICE_FONT_STATUS_UNKNOWN)
+		{
+			LL_DEBUGS("Voice") << "Unknown voice font status: " << font_status << LL_ENDL;
+		}
+	}
+}
+
+void LLVivoxVoiceClient::expireVoiceFonts()
+{
+	// *TODO: If we are selling voice fonts in packs, there are probably
+	// going to be a number of fonts with the same expiration time, so would
+	// be more efficient to just keep a list of expiration times rather
+	// than checking each font individually.
+
+	bool have_expired = false;
+	bool will_expire = false;
+	bool expired_in_use = false;
+
+	LLUUID current_effect = LLVoiceClient::instance().getVoiceEffectDefault();
+
+	voice_font_map_t::iterator iter;
+	for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter)
+	{
+		voiceFontEntry* voice_font = iter->second;
+		LLTimer& expiry_timer  = voice_font->mExpiryTimer;
+		LLTimer& warning_timer = voice_font->mExpiryWarningTimer;
+
+		// Check for expired voice fonts
+		if (expiry_timer.getStarted() && expiry_timer.hasExpired())
+		{
+			// Check whether it is the active voice font
+			if (voice_font->mID == current_effect)
+			{
+				// Reset to no voice effect.
+				setVoiceEffect(LLUUID::null);
+				expired_in_use = true;
+			}
+			deleteVoiceFont(voice_font->mID);
+			have_expired = true;
+		}
+
+		// Check for voice fonts that will expire in less that the warning time
+		if (warning_timer.getStarted() && warning_timer.hasExpired())
+		{
+			will_expire = true;
+			warning_timer.stop();
+		}
+	}
+
+	LLSD args;
+	args["URL"] = LLTrans::getString("voice_morphing_url");
+
+	// Give a notification if any voice fonts have expired.
+	if (have_expired)
+	{
+		if (expired_in_use)
+		{
+			LLNotificationsUtil::add("VoiceEffectsExpiredInUse", args);
+		}
+		else
+		{
+			LLNotificationsUtil::add("VoiceEffectsExpired", args);
+		}
+
+		// Refresh voice font lists in the UI.
+		notifyVoiceFontObservers(true);
+	}
+
+	// Give a warning notification if any voice fonts are due to expire.
+	if (will_expire)
+	{
+		S32 seconds = gSavedSettings.getS32("VoiceEffectExpiryWarningTime");
+		args["INTERVAL"] = llformat("%d", seconds / SEC_PER_DAY);
+
+		LLNotificationsUtil::add("VoiceEffectsWillExpire", args);
+	}
+}
+
+void LLVivoxVoiceClient::deleteVoiceFont(const LLUUID& id)
+{
+	// Remove the entry from the voice font list.
+	voice_effect_list_t::iterator list_iter = mVoiceFontList.begin();
+	while (list_iter != mVoiceFontList.end())
+	{
+		if (list_iter->second == id)
+		{
+			mVoiceFontList.erase(list_iter++);
+		}
+		else
+		{
+			++list_iter;
+		}
+	}
+
+	// Find the entry in the voice font map and erase its data.
+	voice_font_map_t::iterator map_iter = mVoiceFontMap.find(id);
+	if (map_iter != mVoiceFontMap.end())
+	{
+		delete map_iter->second;
+	}
+
+	// Remove the entry from the voice font map.
+	mVoiceFontMap.erase(map_iter);
+}
+
+void LLVivoxVoiceClient::deleteAllVoiceFonts()
+{
+	mVoiceFontList.clear();
+
+	voice_font_map_t::iterator iter;
+	for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter)
+	{
+		delete iter->second;
+	}
+	mVoiceFontMap.clear();
+}
+
+void LLVivoxVoiceClient::deleteVoiceFontTemplates()
+{
+	mVoiceFontTemplateList.clear();
+
+	voice_font_map_t::iterator iter;
+	for (iter = mVoiceFontTemplateMap.begin(); iter != mVoiceFontTemplateMap.end(); ++iter)
+	{
+		delete iter->second;
+	}
+	mVoiceFontTemplateMap.clear();
+}
+
+S32 LLVivoxVoiceClient::getVoiceFontIndex(const LLUUID& id) const
+{
+	S32 result = 0;
+	if (!id.isNull())
+	{
+		voice_font_map_t::const_iterator it = mVoiceFontMap.find(id);
+		if (it != mVoiceFontMap.end())
+		{
+			result = it->second->mFontIndex;
+		}
+		else
+		{
+			LL_DEBUGS("Voice") << "Selected voice font " << id << " is not available." << LL_ENDL;
+		}
+	}
+	return result;
+}
+
+S32 LLVivoxVoiceClient::getVoiceFontTemplateIndex(const LLUUID& id) const
+{
+	S32 result = 0;
+	if (!id.isNull())
+	{
+		voice_font_map_t::const_iterator it = mVoiceFontTemplateMap.find(id);
+		if (it != mVoiceFontTemplateMap.end())
+		{
+			result = it->second->mFontIndex;
+		}
+		else
+		{
+			LL_DEBUGS("Voice") << "Selected voice font template " << id << " is not available." << LL_ENDL;
+		}
+	}
+	return result;
+}
+
+void LLVivoxVoiceClient::accountGetSessionFontsSendMessage()
+{
+	if(!mAccountHandle.empty())
+	{
+		std::ostringstream stream;
+
+		LL_DEBUGS("Voice") << "Requesting voice font list." << LL_ENDL;
+
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.GetSessionFonts.1\">"
+		<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+		<< "</Request>"
+		<< "\n\n\n";
+
+		writeString(stream.str());
+	}
+}
+
+void LLVivoxVoiceClient::accountGetTemplateFontsSendMessage()
+{
+	if(!mAccountHandle.empty())
+	{
+		std::ostringstream stream;
+
+		LL_DEBUGS("Voice") << "Requesting voice font template list." << LL_ENDL;
+
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.GetTemplateFonts.1\">"
+		<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+		<< "</Request>"
+		<< "\n\n\n";
+
+		writeString(stream.str());
+	}
+}
+
+void LLVivoxVoiceClient::sessionSetVoiceFontSendMessage(sessionState *session)
+{
+	S32 font_index = getVoiceFontIndex(session->mVoiceFontID);
+	LL_DEBUGS("Voice") << "Requesting voice font: " << session->mVoiceFontID << " (" << font_index << "), session handle: " << session->mHandle << LL_ENDL;
+
+	std::ostringstream stream;
+
+	stream
+	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetVoiceFont.1\">"
+	<< "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+	<< "<SessionFontID>" << font_index << "</SessionFontID>"
+	<< "</Request>\n\n\n";
+
+	writeString(stream.str());
+}
+
+void LLVivoxVoiceClient::accountGetSessionFontsResponse(int statusCode, const std::string &statusString)
+{
+	// Voice font list entries were updated via addVoiceFont() during parsing.
+	if(getState() == stateVoiceFontsWait)
+	{
+		setState(stateVoiceFontsReceived);
+	}
+	mVoiceFontsReceived = true;
+
+	notifyVoiceFontObservers(true);
+}
+
+void LLVivoxVoiceClient::accountGetTemplateFontsResponse(int statusCode, const std::string &statusString)
+{
+	// Voice font list entries were updated via addVoiceFont() during parsing.
+	notifyVoiceFontObservers(true);
+}
+void LLVivoxVoiceClient::addObserver(LLVoiceEffectObserver* observer)
+{
+	mVoiceFontObservers.insert(observer);
+}
+
+void LLVivoxVoiceClient::removeObserver(LLVoiceEffectObserver* observer)
+{
+	mVoiceFontObservers.erase(observer);
+}
+
+void LLVivoxVoiceClient::notifyVoiceFontObservers(bool lists_changed)
+{
+	for (voice_font_observer_set_t::iterator it = mVoiceFontObservers.begin();
+		 it != mVoiceFontObservers.end();
+		 )
+	{
+		LLVoiceEffectObserver* observer = *it;
+		observer->onVoiceEffectChanged(lists_changed);
+		// In case onVoiceEffectChanged() deleted an entry.
+		it = mVoiceFontObservers.upper_bound(observer);
+	}
+}
+
+void LLVivoxVoiceClient::enablePreviewBuffer(bool enable)
+{
+	mCaptureBufferMode = enable;
+	if(mCaptureBufferMode && getState() >= stateNoChannel)
+	{
+		LL_DEBUGS("Voice") << "no channel" << LL_ENDL;
+		sessionTerminate();
+	}
+}
+
+void LLVivoxVoiceClient::recordPreviewBuffer()
+{
+	if (!mCaptureBufferMode)
+	{
+		LL_DEBUGS("Voice") << "Not in voice effect preview mode, cannot start recording." << LL_ENDL;
+		mCaptureBufferRecording = false;
+		return;
+	}
+
+	mCaptureBufferRecording = true;
+}
+
+void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id)
+{
+	if (!mCaptureBufferMode)
+	{
+		LL_DEBUGS("Voice") << "Not in voice effect preview mode, no buffer to play." << LL_ENDL;
+		mCaptureBufferRecording = false;
+		return;
+	}
+
+	if (!mCaptureBufferRecorded)
+	{
+		// Can't play until we have something recorded!
+		mCaptureBufferPlaying = false;
+		return;
+	}
+
+	mPreviewVoiceFont = effect_id;
+	mCaptureBufferPlaying = true;
+}
+
+void LLVivoxVoiceClient::stopPreviewBuffer()
+{
+	mCaptureBufferRecording = false;
+	mCaptureBufferPlaying = false;
+}
+
+bool LLVivoxVoiceClient::isPreviewRecording()
+{
+	return (mCaptureBufferMode && mCaptureBufferRecording);
+}
+
+bool LLVivoxVoiceClient::isPreviewPlaying()
+{
+	return (mCaptureBufferMode && mCaptureBufferPlaying);
+}
+
+void LLVivoxVoiceClient::captureBufferRecordStartSendMessage()
+{	if(!mAccountHandle.empty())
+	{
+		std::ostringstream stream;
+
+		LL_DEBUGS("Voice") << "Starting audio capture to buffer." << LL_ENDL;
+
+		// Start capture
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.StartBufferCapture.1\">"
+		<< "</Request>"
+		<< "\n\n\n";
+
+		// Unmute the mic
+		stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
+			<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
+			<< "<Value>false</Value>"
+		<< "</Request>\n\n\n";
+
+		// Dirty the PTT state so that it will get reset when we finishing previewing
+		mPTTDirty = true;
+
+		writeString(stream.str());
+	}
+}
+
+void LLVivoxVoiceClient::captureBufferRecordStopSendMessage()
+{
+	if(!mAccountHandle.empty())
+	{
+		std::ostringstream stream;
+
+		LL_DEBUGS("Voice") << "Stopping audio capture to buffer." << LL_ENDL;
+
+		// Mute the mic. PTT state was dirtied at recording start, so will be reset when finished previewing.
+		stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
+			<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
+			<< "<Value>true</Value>"
+		<< "</Request>\n\n\n";
+
+		// Stop capture
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">"
+			<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+		<< "</Request>"
+		<< "\n\n\n";
+
+		writeString(stream.str());
+	}
+}
+
+void LLVivoxVoiceClient::captureBufferPlayStartSendMessage(const LLUUID& voice_font_id)
+{
+	if(!mAccountHandle.empty())
+	{
+		// Track how may play requests are sent, so we know how many stop events to
+		// expect before play actually stops.
+		++mPlayRequestCount;
+
+		std::ostringstream stream;
+
+		LL_DEBUGS("Voice") << "Starting audio buffer playback." << LL_ENDL;
+
+		S32 font_index = getVoiceFontTemplateIndex(voice_font_id);
+		LL_DEBUGS("Voice") << "With voice font: " << voice_font_id << " (" << font_index << ")" << LL_ENDL;
+
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.PlayAudioBuffer.1\">"
+			<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+			<< "<TemplateFontID>" << font_index << "</TemplateFontID>"
+			<< "<FontDelta />"
+		<< "</Request>"
+		<< "\n\n\n";
+
+		writeString(stream.str());
+	}
+}
+
+void LLVivoxVoiceClient::captureBufferPlayStopSendMessage()
+{
+	if(!mAccountHandle.empty())
+	{
+		std::ostringstream stream;
+
+		LL_DEBUGS("Voice") << "Stopping audio buffer playback." << LL_ENDL;
+
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">"
+			<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+		<< "</Request>"
+		<< "\n\n\n";
+
+		writeString(stream.str());
+	}
+}
 
 LLVivoxProtocolParser::LLVivoxProtocolParser()
 {
@@ -6457,7 +7261,30 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)
 			{
 				LLVivoxVoiceClient::getInstance()->deleteAllAutoAcceptRules();
 			}
-			
+			else if (!stricmp("SessionFont", tag))
+			{
+				id = 0;
+				nameString.clear();
+				descriptionString.clear();
+				expirationDate = LLDate();
+				hasExpired = false;
+				fontType = 0;
+				fontStatus = 0;
+			}
+			else if (!stricmp("TemplateFont", tag))
+			{
+				id = 0;
+				nameString.clear();
+				descriptionString.clear();
+				expirationDate = LLDate();
+				hasExpired = false;
+				fontType = 0;
+				fontStatus = 0;
+			}
+			else if (!stricmp("MediaCompletionType", tag))
+			{
+				mediaCompletionType.clear();
+			}
 		}
 	}
 	responseDepth++;
@@ -6603,8 +7430,43 @@ void LLVivoxProtocolParser::EndTag(const char *tag)
 			subscriptionHandle = string;
 		else if (!stricmp("SubscriptionType", tag))
 			subscriptionType = string;
-		
-	
+		else if (!stricmp("SessionFont", tag))
+		{
+			LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, false);
+		}
+		else if (!stricmp("TemplateFont", tag))
+		{
+			LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, true);
+		}
+		else if (!stricmp("ID", tag))
+		{
+			id = strtol(string.c_str(), NULL, 10);
+		}
+		else if (!stricmp("Description", tag))
+		{
+			descriptionString = string;
+		}
+		else if (!stricmp("ExpirationDate", tag))
+		{
+			expirationDate = vivoxTimeStampToLLDate(string);
+		}
+		else if (!stricmp("Expired", tag))
+		{
+			hasExpired = !stricmp(string.c_str(), "1");
+		}
+		else if (!stricmp("Type", tag))
+		{
+			fontType = strtol(string.c_str(), NULL, 10);
+		}
+		else if (!stricmp("Status", tag))
+		{
+			fontStatus = strtol(string.c_str(), NULL, 10);
+		}
+		else if (!stricmp("MediaCompletionType", tag))
+		{
+			mediaCompletionType = string;;
+		}
+
 		textBuffer.clear();
 		accumulateText= false;
 		
@@ -6633,6 +7495,34 @@ void LLVivoxProtocolParser::CharData(const char *buffer, int length)
 
 // --------------------------------------------------------------------------------
 
+LLDate LLVivoxProtocolParser::vivoxTimeStampToLLDate(const std::string& vivox_ts)
+{
+	// First check to see if it actually already is a proper ISO8601 date,
+	// in case the format miraculously changes in future ;)
+	LLDate ts(vivox_ts);
+	if (ts.notNull())
+	{
+		return ts;
+	}
+
+	std::string time_stamp = vivox_ts;
+
+	// Vivox's format is missing a T from being standard ISO 8601,
+	// so add it.  It is the only space in their result.
+	LLStringUtil::replaceChar(time_stamp, ' ', 'T');
+
+	//also need to remove the hours away from GMT to be compatible
+	//with LLDate as well as the fractions of seconds
+	time_stamp = time_stamp.substr(0, time_stamp.length() - 5);
+
+	//it also needs a 'Z' at the end
+	time_stamp += "Z";
+
+	return LLDate(time_stamp);
+}
+
+// --------------------------------------------------------------------------------
+
 void LLVivoxProtocolParser::processResponse(std::string tag)
 {
 	LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL;
@@ -6686,7 +7576,17 @@ void LLVivoxProtocolParser::processResponse(std::string tag)
 			 </Event>
 			 */
 			LLVivoxVoiceClient::getInstance()->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming);
-		}		
+		}
+		else if (!stricmp(eventTypeCstr, "MediaCompletionEvent"))
+		{
+			/*
+			<Event type="MediaCompletionEvent">
+			<SessionGroupHandle />
+			<MediaCompletionType>AuxBufferAudioCapture</MediaCompletionType>
+			</Event>
+			*/
+			LLVivoxVoiceClient::getInstance()->mediaCompletionEvent(sessionGroupHandle, mediaCompletionType);
+		}
 		else if (!stricmp(eventTypeCstr, "TextStreamUpdatedEvent"))
 		{
 			/*
@@ -6747,6 +7647,9 @@ void LLVivoxProtocolParser::processResponse(std::string tag)
 		}
 		else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent"))
 		{
+			// These are really spamming in tuning mode
+			squelchDebugOutput = true;
+
 			LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy);
 		}
 		else if (!stricmp(eventTypeCstr, "BuddyPresenceEvent"))
@@ -6861,6 +7764,14 @@ void LLVivoxProtocolParser::processResponse(std::string tag)
 			// We don't need to process these, but they're so spammy we don't want to log them.
 			squelchDebugOutput = true;
 		}
+		else if (!stricmp(actionCstr, "Account.GetSessionFonts.1"))
+		{
+			LLVivoxVoiceClient::getInstance()->accountGetSessionFontsResponse(statusCode, statusString);
+		}
+		else if (!stricmp(actionCstr, "Account.GetTemplateFonts.1"))
+		{
+			LLVivoxVoiceClient::getInstance()->accountGetTemplateFontsResponse(statusCode, statusString);
+		}
 		/*
 		 else if (!stricmp(actionCstr, "Account.ChannelGetList.1"))
 		 {
diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h
index 59fec8b954942e6d57dd028f0918d0fb7ac4e6f1..d71fe132c588e99d1e1b039b75b809da77841364 100644
--- a/indra/newview/llvoicevivox.h
+++ b/indra/newview/llvoicevivox.h
@@ -57,15 +57,9 @@ class LLVivoxVoiceClientMuteListObserver;
 class LLVivoxVoiceClientFriendsObserver;	
 
 
-class LLVivoxVoiceClientParticipantObserver
-{
-public:
-	virtual ~LLVivoxVoiceClientParticipantObserver() { }
-	virtual void onChange() = 0;
-};
-
-
-class LLVivoxVoiceClient: public LLSingleton<LLVivoxVoiceClient>, virtual public LLVoiceModuleInterface
+class LLVivoxVoiceClient :	public LLSingleton<LLVivoxVoiceClient>,
+							virtual public LLVoiceModuleInterface,
+							virtual public LLVoiceEffectInterface
 {
 	LOG_CLASS(LLVivoxVoiceClient);
 public:
@@ -84,7 +78,7 @@ public:
 	virtual void updateSettings(); // call after loading settings and whenever they change
 
 	// Returns true if vivox has successfully logged in and is not in error state	
-	virtual bool isVoiceWorking();
+	virtual bool isVoiceWorking() const;
 
 	/////////////////////
 	/// @name Tuning
@@ -232,15 +226,49 @@ public:
 	virtual void removeObserver(LLFriendObserver* observer);		
 	virtual void addObserver(LLVoiceClientParticipantObserver* observer);
 	virtual void removeObserver(LLVoiceClientParticipantObserver* observer);
-	
-	
-	
 	//@}
 	
 	virtual std::string sipURIFromID(const LLUUID &id);
 	//@}
 
-				
+	/// @name LLVoiceEffectInterface virtual implementations
+	///  @see LLVoiceEffectInterface
+	//@{
+
+	//////////////////////////
+	/// @name Accessors
+	//@{
+	virtual bool setVoiceEffect(const LLUUID& id);
+	virtual const LLUUID getVoiceEffect();
+	virtual LLSD getVoiceEffectProperties(const LLUUID& id);
+
+	virtual void refreshVoiceEffectLists(bool clear_lists);
+	virtual const voice_effect_list_t& getVoiceEffectList() const;
+	virtual const voice_effect_list_t& getVoiceEffectTemplateList() const;
+	//@}
+
+	//////////////////////////////
+	/// @name Status notification
+	//@{
+	virtual void addObserver(LLVoiceEffectObserver* observer);
+	virtual void removeObserver(LLVoiceEffectObserver* observer);
+	//@}
+
+	//////////////////////////////
+	/// @name Effect preview buffer
+	//@{
+	virtual void enablePreviewBuffer(bool enable);
+	virtual void recordPreviewBuffer();
+	virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null);
+	virtual void stopPreviewBuffer();
+
+	virtual bool isPreviewRecording();
+	virtual bool isPreviewPlaying();
+	//@}
+
+	//@}
+
+
 protected:
 	//////////////////////
 	// Vivox Specific definitions	
@@ -278,14 +306,13 @@ protected:
 		bool mIsSpeaking;
 		bool mIsModeratorMuted;
 		bool mOnMuteList;		// true if this avatar is on the user's mute list (and should be muted)
-	       bool mVolumeSet;		// true if incoming volume messages should not change the volume
+		bool mVolumeSet;		// true if incoming volume messages should not change the volume
 		bool mVolumeDirty;		// true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed)
 		bool mAvatarIDValid;
 		bool mIsSelf;
 	};
 	
 	typedef std::map<const std::string, participantState*> participantMap;
-	
 	typedef std::map<const LLUUID, participantState*> participantUUIDMap;
 	
 	struct sessionState
@@ -332,14 +359,17 @@ protected:
 		bool		mIncoming;
 		bool		mVoiceEnabled;
 		bool		mReconnect;	// Whether we should try to reconnect to this session if it's dropped
-		// Set to true when the mute state of someone in the participant list changes.
+
+		// Set to true when the volume/mute state of someone in the participant list changes.
 		// The code will have to walk the list to find the changed participant(s).
 		bool		mVolumeDirty;
-	        bool		mMuteDirty;
-		
+		bool		mMuteDirty;
+
 		bool		mParticipantsChanged;
 		participantMap mParticipantsByURI;
 		participantUUIDMap mParticipantsByUUID;
+
+		LLUUID		mVoiceFontID;
 	};
 
 	// internal state for a simple state machine.  This is used to deal with the asynchronous nature of some of the messages.
@@ -356,6 +386,11 @@ protected:
 		stateMicTuningStart,
 		stateMicTuningRunning,		
 		stateMicTuningStop,
+		stateCaptureBufferPaused,
+		stateCaptureBufferRecStart,
+		stateCaptureBufferRecording,
+		stateCaptureBufferPlayStart,
+		stateCaptureBufferPlaying,
 		stateConnectorStart,		// connector needs to be started
 		stateConnectorStarting,		// waiting for connector handle
 		stateConnectorStarted,		// connector handle received
@@ -364,6 +399,8 @@ protected:
 		stateNeedsLogin,			// send login request
 		stateLoggingIn,				// waiting for account handle
 		stateLoggedIn,				// account handle received
+		stateVoiceFontsWait,		// Awaiting the list of voice fonts
+		stateVoiceFontsReceived,	// List of voice fonts received
 		stateCreatingSessionGroup,	// Creating the main session group
 		stateNoChannel,				// 
 		stateJoiningSession,		// waiting for session handle
@@ -436,8 +473,6 @@ protected:
 	void tuningCaptureStartSendMessage(int duration);
 	void tuningCaptureStopSendMessage();
 
-	bool inTuningStates();
-
 	//----------------------------------
 	// devices
 	void clearCaptureDevices();
@@ -464,6 +499,7 @@ protected:
 	void connectorShutdownResponse(int statusCode, std::string &statusString);
 
 	void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state);
+	void mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType);
 	void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming);
 	void textStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, bool enabled, int state, bool incoming);
 	void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString);
@@ -591,8 +627,8 @@ protected:
 	void deleteAllAutoAcceptRules(void);
 	void addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy);
 	void accountListBlockRulesResponse(int statusCode, const std::string &statusString);						
-	void accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString);						
-	
+	void accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString);
+
 	/////////////////////////////
 	// session control messages
 
@@ -621,7 +657,21 @@ protected:
 	void lookupName(const LLUUID &id);
 	static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group);
 	void avatarNameResolved(const LLUUID &id, const std::string &name);
-		
+
+	/////////////////////////////
+	// Voice fonts
+
+	void addVoiceFont(const S32 id,
+					  const std::string &name,
+					  const std::string &description,
+					  const LLDate &expiration_date,
+					  const bool has_expired,
+					  const S32 font_type,
+					  const S32 font_status,
+					  const bool template_font = false);
+	void accountGetSessionFontsResponse(int statusCode, const std::string &statusString);
+	void accountGetTemplateFontsResponse(int statusCode, const std::string &statusString); 
+
 private:
 	LLVoiceVersionInfo mVoiceVersion;
 		
@@ -804,6 +854,87 @@ private:
 	typedef std::set<LLFriendObserver*> friend_observer_set_t;
 	friend_observer_set_t mFriendObservers;
 	void notifyFriendObservers();
+
+	// Voice Fonts
+
+	void expireVoiceFonts();
+	void deleteVoiceFont(const LLUUID& id);
+	void deleteAllVoiceFonts();
+	void deleteVoiceFontTemplates();
+
+	S32 getVoiceFontIndex(const LLUUID& id) const;
+	S32 getVoiceFontTemplateIndex(const LLUUID& id) const;
+
+	void accountGetSessionFontsSendMessage();
+	void accountGetTemplateFontsSendMessage();
+	void sessionSetVoiceFontSendMessage(sessionState *session);
+
+	void notifyVoiceFontObservers(bool lists_changed = false);
+
+	typedef enum e_voice_font_type
+	{
+		VOICE_FONT_TYPE_NONE = 0,
+		VOICE_FONT_TYPE_ROOT = 1,
+		VOICE_FONT_TYPE_USER = 2,
+		VOICE_FONT_TYPE_UNKNOWN
+	} EVoiceFontType;
+
+	typedef enum e_voice_font_status
+	{
+		VOICE_FONT_STATUS_NONE = 0,
+		VOICE_FONT_STATUS_FREE = 1,
+		VOICE_FONT_STATUS_NOT_FREE = 2,
+		VOICE_FONT_STATUS_UNKNOWN
+	} EVoiceFontStatus;
+
+	struct voiceFontEntry
+	{
+		voiceFontEntry(LLUUID& id);
+		~voiceFontEntry();
+
+		LLUUID		mID;
+		S32			mFontIndex;
+		std::string mName;
+		LLDate		mExpirationDate;
+		S32			mFontType;
+		S32			mFontStatus;
+		bool		mIsNew;
+
+		LLTimer		mExpiryTimer;
+		LLTimer		mExpiryWarningTimer;
+	};
+
+	bool mVoiceFontsReceived;
+	bool mVoiceFontsNew;
+	voice_effect_list_t	mVoiceFontList;
+	voice_effect_list_t	mVoiceFontTemplateList;
+
+	typedef std::map<const LLUUID, voiceFontEntry*> voice_font_map_t;
+	voice_font_map_t	mVoiceFontMap;
+	voice_font_map_t	mVoiceFontTemplateMap;
+
+	typedef std::set<LLVoiceEffectObserver*> voice_font_observer_set_t;
+	voice_font_observer_set_t mVoiceFontObservers;
+
+	LLTimer	mVoiceFontExpiryTimer;
+
+
+	// Audio capture buffer
+
+	void captureBufferRecordStartSendMessage();
+	void captureBufferRecordStopSendMessage();
+	void captureBufferPlayStartSendMessage(const LLUUID& voice_font_id = LLUUID::null);
+	void captureBufferPlayStopSendMessage();
+
+	bool mCaptureBufferMode;		// Disconnected from voice channels while using the capture buffer.
+	bool mCaptureBufferRecording;	// A voice sample is being captured.
+	bool mCaptureBufferRecorded;	// A voice sample is captured in the buffer ready to play.
+	bool mCaptureBufferPlaying;		// A voice sample is being played.
+
+	LLTimer	mCaptureTimer;
+	LLUUID mPreviewVoiceFont;
+	LLUUID mPreviewVoiceFontLast;
+	S32 mPlayRequestCount;
 };
 
 /** 
@@ -890,7 +1021,13 @@ protected:
 	int				numberOfAliases;
 	std::string		subscriptionHandle;
 	std::string		subscriptionType;
-	
+	S32				id;
+	std::string		descriptionString;
+	LLDate			expirationDate;
+	bool			hasExpired;
+	S32				fontType;
+	S32				fontStatus;
+	std::string		mediaCompletionType;
 	
 	// Members for processing text between tags
 	std::string		textBuffer;
@@ -907,11 +1044,9 @@ protected:
 	void			StartTag(const char *tag, const char **attr);
 	void			EndTag(const char *tag);
 	void			CharData(const char *buffer, int length);
-	
+	LLDate			vivoxTimeStampToLLDate(const std::string& vivox_ts);
 };
 
 
 #endif //LL_VIVOX_VOICE_CLIENT_H
 
-
-
diff --git a/indra/newview/skins/default/xui/en/floater_voice_controls.xml b/indra/newview/skins/default/xui/en/floater_voice_controls.xml
index 5b77f11d710584e98153d8fb98be9ffbcf2a0000..0569b4d5151df0ff73dddcfacc914c19f9cb68ec 100644
--- a/indra/newview/skins/default/xui/en/floater_voice_controls.xml
+++ b/indra/newview/skins/default/xui/en/floater_voice_controls.xml
@@ -3,7 +3,7 @@
  can_resize="true"
  can_minimize="true"
  can_close="false"
- height="202"
+ height="205"
  layout="topleft"
  min_height="124"
  min_width="190"
@@ -50,7 +50,7 @@
           user_resize="false" 
          auto_resize="false" 
          layout="topleft"
-         height="26"
+         height="20"
          name="my_panel">
             <avatar_icon
              enabled="false"
@@ -86,23 +86,38 @@
              visible="true"
              width="20" />
         </layout_panel>
-         <layout_panel
-          auto_resize="false"
-          user_resize="false" 
-          follows="top|left"
-          height="26"
-          visible="true"
-          layout="topleft"
-          name="leave_call_btn_panel"
-          width="100">
-           <button
-          follows="right|top"
-            height="23"
-            top_pad="0"
-            label="Leave Call"
-            name="leave_call_btn"
-            width="100" />
-         </layout_panel>
+        <layout_stack
+         clip="true"
+         auto_resize="false"
+         follows="left|top|right"
+         height="26"
+         layout="topleft"
+         mouse_opaque="false"
+         name="voice_effect_and_leave_call_stack"
+         orientation="horizontal"
+         width="262">
+          <panel
+           class="panel_voice_effect"
+           name="panel_voice_effect"
+           visiblity_control="VoiceMorphingEnabled"
+           filename="panel_voice_effect.xml" />
+          <layout_panel
+           auto_resize="false"
+           user_resize="false"
+           follows="top|right"
+           height="23"
+           visible="true"
+           layout="topleft"
+           name="leave_call_btn_panel"
+           width="100">
+            <button
+             follows="right|top"
+             height="23"
+             label="Leave Call"
+             name="leave_call_btn"
+             width="100" />
+          </layout_panel>
+        </layout_stack>
       <layout_panel
           follows="all"
           layout="topleft"
diff --git a/indra/newview/skins/default/xui/en/floater_voice_effect.xml b/indra/newview/skins/default/xui/en/floater_voice_effect.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7335488e73e2872a60bc801fc496e421d94ca6ac
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_voice_effect.xml
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ legacy_header_height="18"
+ can_resize="true"
+ height="415"
+ name="voice_effects"
+ help_topic="voice_effects"
+ title="PREVIEW VOICE MORPHING"
+ background_visible="true"
+ follows="all"
+ label="Places"
+ layout="topleft"
+ min_height="350"
+ min_width="240"
+ width="313">
+  <string name="no_voice_effect">
+    (No Voice Effect)
+  </string>
+  <string name="active_voice_effect">
+    (Active)
+  </string>
+  <string name="new_voice_effect">
+    New!
+  </string>
+  <scroll_list
+   bottom_delta="300"
+   draw_heading="true"
+   follows="all"
+   layout="topleft"
+   left="0"
+   multi_select="false"
+   tool_tip="Record a sample of your voice, then click an effect to preview."
+   top="20"
+   name="voice_effect_list">
+    <scroll_list.columns
+     label="Name"
+     name="name"
+     width="153" />
+    <scroll_list.columns
+     label="New"
+     name="new"
+     width="64" />
+    <scroll_list.columns
+     label="Expires"
+     name="expires"
+     width="100" />
+  </scroll_list>
+  <panel
+   background_visible="true"
+   bevel_style="none"
+   top_pad="0"
+   follows="left|right|bottom"
+   height="30"
+   label="bottom_panel"
+   layout="topleft"
+   left="0"
+   name="bottom_panel"
+   width="313">
+<!--
+    <menu_button
+     follows="bottom|left"
+     height="18"
+     image_disabled="OptionsMenu_Disabled"
+     image_selected="OptionsMenu_Press"
+     image_unselected="OptionsMenu_Off"
+     layout="topleft"
+     left="10"
+     menu_filename="menu_voice_effect_gear.xml"
+     name="gear_btn"
+     top="5"
+     tool_tip="More options"
+     width="18" />
+-->
+    <button
+     follows="bottom|left"
+     font="SansSerifBigBold"
+     height="18"
+     image_selected="AddItem_Press"
+     image_unselected="AddItem_Off"
+     image_disabled="AddItem_Disabled"
+     layout="topleft"
+     left="10"
+     name="add_voice_effect_btn"
+     tool_tip="Get more voice effects"
+     top="5"
+     width="18">
+      <button.commit_callback
+       function="VoiceEffect.Add" />
+    </button>
+<!--
+    <button
+     follows="bottom|left"
+     font="SansSerifBigBold"
+     height="10"
+     image_hover_selected="Activate_Checkmark"
+     image_selected="Activate_Checkmark"
+     image_unselected="Activate_Checkmark"
+     layout="topleft"
+     left_pad="5"
+     name="activate_btn"
+     tool_tip="Activate/Deactivate selected voice effect"
+     top="10"
+     width="10">
+      <button.commit_callback
+       function="VoiceEffect.Activate" />
+    </button>
+-->
+  </panel>
+  <text
+   height="40"
+   word_wrap="true"
+   use_ellipses="true"
+   type="string"
+   text_color="LabelSelectedDisabledColor"
+   length="1"
+   follows="left|bottom|right"
+   layout="topleft"
+   left="6"
+   name="status_text"
+   top_pad="10"
+   width="300">
+    Record a sample of your voice, and then select an effect to preview. Close this window to return to in-world voice.
+  </text>
+  <button
+   follows="left|bottom"
+   height="23"
+   label="Record Sample"
+   layout="topleft"
+   left="6"
+   name="record_btn"
+   tool_tip="Record a sample of your voice."
+   top_pad="5"
+   width="135">
+    <button.commit_callback
+     function="VoiceEffect.Record" />
+  </button>
+  <button
+   follows="left|bottom"
+   height="23"
+   label="Stop"
+   layout="topleft"
+   left_delta="0"
+   name="record_stop_btn"
+   top_delta="0"
+   width="135">
+    <button.commit_callback
+     function="VoiceEffect.Stop" />
+  </button>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 16c2581d63f4cc51f8068d188a7b76c3ed2f9f32..96885bf134b7514988151642b232c3cfb5bf2ced 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -80,6 +80,17 @@
              function="Floater.Toggle"
              parameter="gestures" />
         </menu_item_check>
+        <menu_item_check
+         label="My Voice"
+         name="ShowVoice"
+         visibility_control="VoiceMorphingEnabled">
+            <menu_item_check.on_check
+             function="Floater.Visible"
+             parameter="voice_effect" />
+            <menu_item_check.on_click
+             function="Floater.Toggle"
+             parameter="voice_effect" />
+        </menu_item_check>
         <menu
          label="My Status"
          name="Status"
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 999f804e7191c0d4d8308411eb4a12b1288265d4..6b5659f6c0fdfb35502643c766cd70773216e004 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -5956,6 +5956,39 @@ We are creating a voice channel for you. This may take up to one minute.
     <unique/>
   </notification>
 
+  <notification
+   icon="notify.tga"
+   name="VoiceEffectsExpired"
+   sound="UISndAlert"
+   persist="true"
+   type="notify">
+Subscribed Voice Effects have expired.
+[[URL] Renew your subscription] to reactivate them.
+    <unique/>
+  </notification>
+
+  <notification
+   icon="notify.tga"
+   name="VoiceEffectsExpiredInUse"
+   sound="UISndAlert"
+   persist="true"
+   type="notify">
+The active Voice Effect has expired, your normal voice settings have been applied.
+[[URL] Renew your subscription] to reactivate it.
+    <unique/>
+  </notification>
+
+  <notification
+   icon="notify.tga"
+   name="VoiceEffectsWillExpire"
+   sound="UISndAlert"
+   persist="true"
+   type="notify">
+Voice Effects will expire in less than [INTERVAL] days.
+[[URL] Renew your subscription] or they will be removed.
+    <unique/>
+  </notification>
+
   <notification
    icon="notifytip.tga"
    name="Cannot enter parcel: not a group member"
diff --git a/indra/newview/skins/default/xui/en/panel_voice_effect.xml b/indra/newview/skins/default/xui/en/panel_voice_effect.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9b6548ef9cd93ca04412d576e6c5aa15bb65fc30
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_voice_effect.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<panel
+ follows="all"
+ height="26"
+ layout="topleft"
+ name="panel_voice_effect"
+ width="200">
+  <string name="no_voice_effect">
+    No Voice Effect
+  </string>
+  <string name="preview_voice_effects">
+    Preview Voice Morphing  â–¶
+  </string>
+  <string name="get_voice_effects">
+    Get Voice Morphing  â–¶
+  </string>
+  <combo_box
+   enabled="false"
+   follows="left|top|right"
+   height="23"
+   name="voice_effect"
+   top_pad="0"
+   width="200">
+    <combo_box.item
+     label="No Voice Effect"
+     name="no_voice_effect"
+	 top_pad="0"
+     value="0" />
+    <combo_box.commit_callback
+     function="Voice.CommitVoiceEffect" />
+  </combo_box>
+</panel>
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index f8bb36b88ac2ddac6154bc53473b21fb78004a34..a54dd0292716ecb7213b0c6a4ff7de60da2edba8 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -3086,10 +3086,12 @@ If you continue to receive this message, contact the [SUPPORT_SITE].
   <string name="unread_chat_multiple">
     [SOURCES] have said something new
   </string>"
-	<string name="session_initialization_timed_out_error">
-		The session initialization is timed out
-	</string>
-  
+  <string name="session_initialization_timed_out_error">
+    The session initialization is timed out
+  </string>
+
+  <string name="voice_morphing_url">http://secondlife.com/landing/v0icem0rphingt3st</string>
+
   <!-- Financial operations strings -->
   <string name="paid_you_ldollars">[NAME] paid you L$[AMOUNT]</string>
   <string name="you_paid_ldollars">You paid [NAME] L$[AMOUNT] [REASON].</string>