Commits

Aimee Linden  committed c08baaf Merge

Automated merge with ssh://hg.lindenlab.com/dessie/viewer-public

  • Participants
  • Parent commits 7cf8aab, c13cda3

Comments (0)

Files changed (25)

File indra/newview/CMakeLists.txt

     llfloaterurldisplay.cpp
     llfloaterurlentry.cpp
     llfloatervoicedevicesettings.cpp
+    llfloatervoiceeffect.cpp
     llfloaterwater.cpp
     llfloaterwhitelistentry.cpp
     llfloaterwindlight.cpp
     llpanelprofileview.cpp
     llpanelteleporthistory.cpp
     llpaneltiptoast.cpp
+    llpanelvoiceeffect.cpp
     llpanelvolume.cpp
     llpanelvolumepulldown.cpp
     llparcelselection.cpp
     llfloaterurldisplay.h
     llfloaterurlentry.h
     llfloatervoicedevicesettings.h
+    llfloatervoiceeffect.h
     llfloaterwater.h
     llfloaterwhitelistentry.h
     llfloaterwindlight.h
     llpanelprofileview.h
     llpanelteleporthistory.h
     llpaneltiptoast.h
+    llpanelvoiceeffect.h
     llpanelvolume.h
     llpanelvolumepulldown.h
     llparcelselection.h

File indra/newview/app_settings/logcontrol.xml

 						</array>
 					<key>tags</key>
 						<array>
+							<string>Voice</string>
 						</array>
 				</map>
 			</array>

File indra/newview/app_settings/settings.xml

       <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>

File indra/newview/app_settings/settings_per_account.xml

         <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

File indra/newview/llcallfloater.cpp

 	return new LLNonAvatarCaller;
 }
 
-LLVoiceChannel* LLCallFloater::sCurrentVoiceCanel = NULL;
+LLVoiceChannel* LLCallFloater::sCurrentVoiceChannel = NULL;
 
 LLCallFloater::LLCallFloater(const LLSD& key)
 : LLTransientDockableFloater(NULL, false, 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
 
 	initAgentData();
 
-
 	connectToChannel(LLVoiceChannel::getCurrentVoiceChannel());
 
 	setIsChrome(true);
 }
 
 // virtual
-void LLCallFloater::onChange()
+void LLCallFloater::onParticipantsChanged()
 {
 	if (NULL == mParticipants) return;
 	updateParticipantsVoiceState();
 
 	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())
 	// *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");
 
 {
 	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());
 }
 
 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();

File indra/newview/llcallfloater.h

 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
 {
 	 *
 	 * Refreshes list to display participants not in voice as disabled.
 	 */
-	/*virtual*/ void onChange();
+	/*virtual*/ void onParticipantsChanged();
 
 	static void sOnCurrentChannelChanged(const LLUUID& session_id);
 
 	 *
 	 * @see sOnCurrentChannelChanged()
 	 */
-	static LLVoiceChannel* sCurrentVoiceCanel;
+	static LLVoiceChannel* sCurrentVoiceChannel;
 
 	/* virtual */
 	LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::IM; }

File indra/newview/llfloatervoiceeffect.cpp

+/** 
+ * @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);
+	childSetTextArg("voice_morphing_link", "[URL]", LLTrans::getString("voice_morphing_url"));
+
+	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);
+		}
+	}
+
+	// Add each Voice Effect template, if there are any (template list includes all usable effects)
+	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");
+			}
+
+			// Tag available effects that are new this session
+			if (effect_properties["is_new"].asBoolean())
+			{
+				effect_name += " " + getString("new_voice_effect");
+			}
+
+			LLDate expiry_date = effect_properties["expiry_date"].asDate();
+			bool is_template_only = effect_properties["template_only"].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"] = "expires";
+			if (!is_template_only)
+			{
+				element["columns"][1]["value"] = expiry_date;
+				element["columns"][1]["type"] = "date";
+			}
+			else {
+				element["columns"][1]["value"] = "";
+			}
+			element["columns"][1]["font"]["name"] = "SANSSERIF";
+			element["columns"][1]["font"]["style"] = "NORMAL";
+
+			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;
+				dynamic_cast<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;
+
+	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+	if (effect_interface)
+	{
+		recording = effect_interface->isPreviewRecording();
+	}
+
+	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());
+//	}
+//}
+

File indra/newview/llfloatervoiceeffect.h

+/** 
+ * @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

File indra/newview/llpanelvoiceeffect.cpp

+/** 
+ * @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 "lltransientfloatermgr.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()
+{
+	LLView* combo_list_view = mVoiceEffectCombo->getChildView("ComboBox");
+	LLTransientFloaterMgr::getInstance()->removeControlView(combo_list_view);
+
+	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");
+
+	// Need to tell LLTransientFloaterMgr about the combo list, otherwise it can't
+	// be clicked while in a docked floater as it extends outside the floater area.
+	LLView* combo_list_view = mVoiceEffectCombo->getChildView("ComboBox");
+	LLTransientFloaterMgr::getInstance()->addControlView(combo_list_view);
+
+	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+	if (effect_interface)
+	{
+		effect_interface->addObserver(this);
+	}
+
+	update(true);
+
+	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(effect_list_updated);
+}
+
+void LLPanelVoiceEffect::update(bool list_updated)
+{
+	if (mVoiceEffectCombo)
+	{
+		LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface();
+		if (list_updated)
+		{
+			// Add the default "No Voice Effect" entry.
+			mVoiceEffectCombo->removeall();
+			mVoiceEffectCombo->add(getString("no_voice_effect"), LLUUID::null);
+			mVoiceEffectCombo->addSeparator();
+
+			// Add entries for each Voice Effect.
+			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();
+			}
+
+			// Add the fixed entries to go to the preview floater or marketing page.
+			mVoiceEffectCombo->add(getString("preview_voice_effects"), PREVIEW_VOICE_EFFECTS);
+			mVoiceEffectCombo->add(getString("get_voice_effects"), GET_VOICE_EFFECTS);
+		}
+
+		if (effect_interface && LLVoiceClient::instance().isVoiceWorking())
+		{
+			// Select the current voice effect.
+			mVoiceEffectCombo->setValue(effect_interface->getVoiceEffect());
+			mVoiceEffectCombo->setEnabled(true);
+		}
+		else
+		{
+			// If voice isn't working or Voice Effects are not supported disable the control.
+			mVoiceEffectCombo->setValue(LLUUID::null);
+			mVoiceEffectCombo->setEnabled(false);
+		}
+	}
+}

File indra/newview/llpanelvoiceeffect.h

+/** 
+ * @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(bool list_updated);
+
+	/// 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

File indra/newview/llparticipantlist.cpp

 		mAvalineCallers.insert(avaline_caller_id);
 	}
 
-	void onChange()
+	void onParticipantsChanged()
 	{
 		uuid_set_t participant_uuids;
 		LLVoiceClient::getInstance()->getParticipantList(participant_uuids);

File indra/newview/llspeakingindicatormanager.cpp

 	 * 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
 	mSwitchedIndicatorsOn.clear();
 }
 
-void SpeakingIndicatorManager::onChange()
+void SpeakingIndicatorManager::onParticipantsChanged()
 {
 	LL_DEBUGS("SpeakingIndicator") << "Voice participant list was changed, updating indicators" << LL_ENDL;
 

File indra/newview/llviewerfloaterreg.cpp

 #include "llfloateruipreview.h"
 #include "llfloaterurldisplay.h"
 #include "llfloatervoicedevicesettings.h"
+#include "llfloatervoiceeffect.h"
 #include "llfloaterwater.h"
 #include "llfloaterwhitelistentry.h"
 #include "llfloaterwindlight.h"
 	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>);	

File indra/newview/llvoicechannel.cpp

 	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));
 	}
 	

File indra/newview/llvoicechannel.h

 	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;

File indra/newview/llvoiceclient.cpp

 #include "llviewerwindow.h"
 #include "llvoicevivox.h"
 #include "llviewernetwork.h"
+#include "llcommandhandler.h"
 #include "llhttpnode.h"
 #include "llnotificationsutil.h"
 #include "llsdserialize.h"
 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";
 
 
 
-
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-LLVoiceClient::LLVoiceClient()
+LLVoiceClient::LLVoiceClient() :
+	mVoiceModule(NULL),
+	mVoiceEffectEnabled(LLCachedControl<bool>(gSavedSettings, "VoiceMorphingEnabled")),
+	mVoiceEffectDefault(LLCachedControl<std::string>(gSavedPerAccountSettings, "VoiceEffectDefault"))
 {
-	mVoiceModule = NULL;
 }
 
 //---------------------------------------------------
 	}
 }
 
-bool LLVoiceClient::isVoiceWorking()
+bool LLVoiceClient::isVoiceWorking() const
 {
 	if (mVoiceModule) 
 	{
 	}
 }
 
+LLVoiceEffectInterface* LLVoiceClient::getVoiceEffectInterface() const
+{
+	return getVoiceEffectEnabled() ? dynamic_cast<LLVoiceEffectInterface*>(mVoiceModule) : NULL;
+}
 
 ///////////////////
 // version checking

File indra/newview/llvoiceclient.h

 #include "llviewerregion.h"
 #include "llcallingcard.h"   // for LLFriendObserver
 #include "llsecapi.h"
+#include "llcontrol.h"
 
 // devices
 
 {
 public:
 	virtual ~LLVoiceClientParticipantObserver() { }
-	virtual void onChange() = 0;
+	virtual void onParticipantsChanged() = 0;
 };
 
 
 	
 	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;
 	
 	//////////////////////////
 	/// @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;	
 };
 
 
+//////////////////////////////////
+/// @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);
 
 	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();
 	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;
 };
 
 /**

File indra/newview/llvoicevivox.cpp

 #include "llviewerparcelmgr.h"
 //#include "llfirstuse.h"
 #include "llspeakers.h"
+#include "lltrans.h"
 #include "llviewerwindow.h"
 #include "llviewercamera.h"
 
 #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;
 // 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;
 
-
-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);
-}
+// 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 int scale_mic_volume(float volume)
 {
 	mBuddyListMapPopulated(false),
 	mBlockRulesListReceived(false),
 	mAutoAcceptRulesListReceived(false),
+
 	mCaptureDeviceDirty(false),
 	mRenderDeviceDirty(false),
 	mSpatialCoordsDirty(false),
 	mVoiceEnabled(false),
 	mWriteInProgress(false),
 
-	mLipSyncEnabled(false)
-
-
-
+	mLipSyncEnabled(false),
+
+	mVoiceFontsReceived(false),
+	mVoiceFontsNew(false),
+	mVoiceFontListDirty(false),
+
+	mCaptureBufferMode(false),
+	mCaptureBufferRecording(false),
+	mCaptureBufferRecorded(false),
+	mCaptureBufferPlaying(false),
+	mPlayRequestCount(0)
 {	
 	mSpeakerVolume = scale_speaker_volume(0);
 
 		CASE(stateMicTuningStart);
 		CASE(stateMicTuningRunning);
 		CASE(stateMicTuningStop);
+		CASE(stateCaptureBufferPaused);
+		CASE(stateCaptureBufferRecStart);
+		CASE(stateCaptureBufferRecording);
+		CASE(stateCaptureBufferPlayStart);
+		CASE(stateCaptureBufferPlaying);
 		CASE(stateConnectorStart);
 		CASE(stateConnectorStarting);
 		CASE(stateConnectorStarted);
 		CASE(stateNeedsLogin);
 		CASE(stateLoggingIn);
 		CASE(stateLoggedIn);
+		CASE(stateVoiceFontsWait);
+		CASE(stateVoiceFontsReceived);
 		CASE(stateCreatingSessionGroup);
 		CASE(stateNoChannel);
 		CASE(stateJoiningSession);
 			// Clean up and reset everything. 
 			closeSocket();
 			deleteAllSessions();
-			deleteAllBuddies();		
-			
+			deleteAllBuddies();
+			deleteAllVoiceFonts();
+			deleteVoiceFontTemplates();
+
 			mConnectorHandle.clear();
 			mAccountHandle.clear();
 			mAccountPassword.clear();
 			
 		}
 		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();
+
+			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();
+
+				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();
+
+			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();
+
+				setState(stateCaptureBufferPaused);
+			}
+		break;
+
+			//MARK: stateConnectorStart
 		case stateConnectorStart:
 			if(!mVoiceEnabled)
 			{
 
 			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();
 			
 					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
+			setState(stateCreatingSessionGroup);
 			sessionGroupCreateSendMessage();
-			
-			setState(stateCreatingSessionGroup);
 #else
 			// Not using session groups -- skip the stateCreatingSessionGroup state.
 			setState(stateNoChannel);
 				mTuningExitState = stateNoChannel;
 				setState(stateMicTuningStart);
 			}
+			else if(mCaptureBufferMode)
+			{
+				setState(stateCaptureBufferPaused);
+			}
 			else if(sessionNeedsRelog(mNextAudioSession))
 			{
 				requestRelog();
 				sessionState *oldSession = mAudioSession;
 
 				mAudioSession = mNextAudioSession;
+				mAudioSessionChanged = true;
 				if(!mAudioSession->mReconnect)	
 				{
 					mNextAudioSession = NULL;
 					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.
 			mAccountHandle.clear();
 			deleteAllSessions();
 			deleteAllBuddies();
+			deleteAllVoiceFonts();
+			deleteVoiceFontTemplates();
 
 			if(mVoiceEnabled && !mRelogRequested)
 			{
 
 	}
 	
-	if(mAudioSession && mAudioSession->mParticipantsChanged)
+	if (mAudioSessionChanged)
+	{
+		mAudioSessionChanged = false;
+		notifyParticipantObservers();
+		notifyVoiceFontObservers();
+	}
+	else if (mAudioSession && mAudioSession->mParticipantsChanged)
 	{
 		mAudioSession->mParticipantsChanged = false;
-		mAudioSessionChanged = true;
-	}
-	
-	if(mAudioSessionChanged)
-	{
-		mAudioSessionChanged = false;
 		notifyParticipantObservers();
 	}
 }
 
 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)
 	{
 			<< "<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());
 
 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)
 	{
 		<< "<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"
 
 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;
 	
 	<< "<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";
 
 			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.
 	}
 }
 
+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, 
 			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);
 			}
 		}
 
 	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
 		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())
 		)
 	{
 		LLVoiceClientParticipantObserver* observer = *it;
-		observer->onChange();
-		// In case onChange() deleted an entry.
+		observer->onParticipantsChanged();
+		// In case onParticipantsChanged() deleted an entry.
 		it = mParticipantObservers.upper_bound(observer);
 	}
 }
 	}
 }
 
+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();
+
+	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)
+{
+	mExpiryTimer.stop();
+	mExpiryWarningTimer.stop();
+}
+
+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 (has_expired)
+	{
+		// Remove existing session fonts that have expired since we last saw them.
+		if (!new_font)
+		{
+			LL_DEBUGS("Voice") << "Expired " << (template_font ? "Template " : "")
+			<< expiration_date.asString() << " " << font_id
+			<< " (" << font_index << ") " << name << LL_ENDL;
+
+			if (!template_font)
+			{
+				deleteVoiceFont(font_id);
+			}
+		}
+		return;
+	}
+
+	if (new_font)
+	{
+		// If it is a new font create a new entry.
+		font = new voiceFontEntry(font_id);
+	}
+	else
+	{
+		// Not a new font, update the existing entry
+		font = iter->second;
+	}
+
+	if (font)
+	{
+		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;
+
+		LL_DEBUGS("Voice") << (template_font ? "Template " : "")
+			<< font->mExpirationDate.asString() << " " << font->mID
+			<< " (" << font->mFontIndex << ") " << name << LL_ENDL;
+
+		// Set the expiry timer to trigger a notification when the voice font can no longer be used.
+		font->mExpiryTimer.start();
+		font->mExpiryTimer.setExpiryAt(expiration_date.secondsSinceEpoch());
+
+		if (font->mExpiryTimer.hasExpired())
+		{
+			// Should never happen, but check anyway.
+			LL_DEBUGS("Voice") << "Voice font " << font->mID
+				<< " expired " << font->mExpirationDate.asString()
+				<< " but is not marked expired!" << LL_ENDL;
+		}
+
+		// Set the warning timer to some interval before actual expiry.
+		S32 warning_time = gSavedSettings.getS32("VoiceEffectExpiryWarningTime");
+		if (warning_time != 0)
+		{
+			font->mExpiryWarningTimer.start();
+			F64 expiry_time = (expiration_date.secondsSinceEpoch() - (F64)warning_time);
+			font->mExpiryWarningTimer.setExpiryAt(expiry_time);
+		}
+		else
+		{
+			// Disable the warning timer.
+			font->mExpiryWarningTimer.stop();
+		}
+
+		 // Only flag new session fonts.
+		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));
+		}
+
+		mVoiceFontListDirty = true;
+
+		// Debugging stuff
+
+		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;
+		LLFrameTimer& expiry_timer  = voice_font->mExpiryTimer;
+		LLFrameTimer& 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();
+	}
+
+	// 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)
+		{
+			LL_DEBUGS("Voice") << "Removing " << id << " from the voice font list." << LL_ENDL;
+			mVoiceFontList.erase(list_iter++);
+			mVoiceFontListDirty = true;
+		}
+		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())