diff options
22 files changed, 1873 insertions, 130 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index d77d53f6af..fd12322d37 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -214,6 +214,7 @@ set(viewer_SOURCE_FILES      llfloaterurldisplay.cpp      llfloaterurlentry.cpp      llfloatervoicedevicesettings.cpp +    llfloatervoiceeffect.cpp      llfloaterwater.cpp      llfloaterwhitelistentry.cpp      llfloaterwindlight.cpp @@ -352,6 +353,7 @@ set(viewer_SOURCE_FILES      llpanelprofileview.cpp      llpanelteleporthistory.cpp      llpaneltiptoast.cpp +    llpanelvoiceeffect.cpp      llpanelvolume.cpp      llpanelvolumepulldown.cpp      llparcelselection.cpp @@ -729,6 +731,7 @@ set(viewer_HEADER_FILES      llfloaterurldisplay.h      llfloaterurlentry.h      llfloatervoicedevicesettings.h +    llfloatervoiceeffect.h      llfloaterwater.h      llfloaterwhitelistentry.h      llfloaterwindlight.h @@ -862,6 +865,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 d7bb64ce8a..d3fb958638 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 f71662a7c8..defe928422 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -10606,6 +10606,28 @@        <key>Value</key>        <integer>0</integer>      </map> +    <key>VoiceFontsAvailable</key> +    <map> +      <key>Comment</key> +      <string>Temporary debug setting to test UI with no voice effects available.</string> +      <key>Persist</key> +      <integer>0</integer> +      <key>Type</key> +      <string>Boolean</string> +      <key>Value</key> +      <integer>1</integer> +    </map> +    <key>VoiceEffectEnabled</key> +    <map> +      <key>Comment</key> +      <string>Whether or not to use 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 3ce32a05b0..c94ae1fca1 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 dd99c6564c..60a2392d87 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 0a8ea7de39..e4341175e2 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/llfloatervoiceeffect.cpp b/indra/newview/llfloatervoiceeffect.cpp new file mode 100644 index 0000000000..60831936ca --- /dev/null +++ b/indra/newview/llfloatervoiceeffect.cpp @@ -0,0 +1,259 @@ +/**  + * @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.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"); + +	mVoiceEffectList = getChild<LLScrollListCtrl>("voice_effect_list"); +	if (mVoiceEffectList) +	{ +		mVoiceEffectList->setDoubleClickCallback(boost::bind(&LLFloaterVoiceEffect::onClickActivate, this)); +	} + +	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); +	if (effect_interface) +	{ +		effect_interface->addObserver(this); +	} + +	update(); + +	return TRUE; +} + +// virtual +void LLFloaterVoiceEffect::onClose(bool app_quitting) +{ +	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); +	if (effect_interface) +	{ +		effect_interface->clearPreviewBuffer(); +	} +} + +void LLFloaterVoiceEffect::update() +{ +	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"] = "BOLD"; + +		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); +			bool is_template_only = effect_properties["template_only"].asBoolean(); +			bool is_new = effect_properties["is_new"].asBoolean(); +			std::string expiry_date = effect_properties["expiry_date"].asString(); + +			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"; +			element["columns"][2]["value"] = !is_template_only ? expiry_date : ""; +			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->setValue(effect_interface->getVoiceEffect()); +	mVoiceEffectList->setEnabled(true); + +	// Update button states +	// *TODO: Should separate this from rebuilding the effects list, to avoid rebuilding it unnecessarily +	bool recording = effect_interface->isPreviewRecording(); +	getChild<LLButton>("record_btn")->setVisible(!recording); +	getChild<LLButton>("record_stop_btn")->setVisible(recording); + +	getChild<LLButton>("play_btn")->setEnabled(effect_interface->isPreviewReady()); +	bool playing = effect_interface->isPreviewPlaying(); +	getChild<LLButton>("play_btn")->setVisible(!playing); +	getChild<LLButton>("play_stop_btn")->setVisible(playing); +} + +// virtual +void LLFloaterVoiceEffect::onVoiceEffectChanged(bool new_effects) +{ +	update(); +} + +void LLFloaterVoiceEffect::onClickRecord() +{ +	LL_DEBUGS("Voice") << "Record clicked" << LL_ENDL; +	LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); +	if (effect_interface) +	{ +		bool record = !effect_interface->isPreviewRecording(); +		effect_interface->recordPreviewBuffer(record); +		getChild<LLButton>("record_btn")->setVisible(!record); +		getChild<LLButton>("record_stop_btn")->setVisible(record); +	} +} + +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) +	{ +		bool play = !effect_interface->isPreviewPlaying(); +		effect_interface->playPreviewBuffer(play, effect_id); +		getChild<LLButton>("play_btn")->setVisible(!play); +		getChild<LLButton>("play_stop_btn")->setVisible(play); +	} +} + +void LLFloaterVoiceEffect::onClickAdd() +{ +	// Open the voice morphing info web page +	LLWeb::loadURL(getString("get_voice_effects_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 0000000000..5ad64f0e26 --- /dev/null +++ b/indra/newview/llfloatervoiceeffect.h @@ -0,0 +1,70 @@ +/**  + * @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 update(); + +	/// Called by voice effect provider when voice effect list is changed. +	virtual void onVoiceEffectChanged(bool new_effects); + +	void onClickRecord(); +	void onClickPlay(); + 	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 0000000000..6210fc7cc7 --- /dev/null +++ b/indra/newview/llpanelvoiceeffect.cpp @@ -0,0 +1,149 @@ +/**  + * @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 "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() == GET_VOICE_EFFECTS) +	{ +		// Open the voice morphing info web page +		LLWeb::loadURL(getString("get_voice_effects_url")); +	} +	else if (value.asInteger() == PREVIEW_VOICE_EFFECTS) +	{ +		// Open the voice effects management floater +		LLFloaterReg::showInstance("voice_effect"); +	} +	else +	{ +		effect_interface->setVoiceEffect(value.asUUID()); +	} + +	mVoiceEffectCombo->setValue(effect_interface->getVoiceEffect()); +} + +// virtual +void LLPanelVoiceEffect::onVoiceEffectChanged(bool new_effects) +{ +	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"), NO_VOICE_EFFECT); +		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("get_voice_effects"), GET_VOICE_EFFECTS); +		mVoiceEffectCombo->add(getString("preview_voice_effects"), PREVIEW_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 0000000000..3ecdcb0d8a --- /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 new_effects); + +	// Fixed entries in the voice effect list +	typedef enum e_voice_effect_combo_items +	{ +		NO_VOICE_EFFECT = 0, +		GET_VOICE_EFFECTS = 1, +		PREVIEW_VOICE_EFFECTS = 2 +	} EVoiceEffectComboItems; +	 +	LLComboBox* mVoiceEffectCombo; +}; + + +#endif //LL_PANELVOICEEFFECT_H diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp index 1117ae05d7..8839d84269 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 29237946d2..ea7601517d 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 506cebfe73..c4a5d6e3fd 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -102,6 +102,7 @@  #include "llfloateruipreview.h"  #include "llfloaterurldisplay.h"  #include "llfloatervoicedevicesettings.h" +#include "llfloatervoiceeffect.h"  #include "llfloaterwater.h"  #include "llfloaterwhitelistentry.h"  #include "llfloaterwindlight.h" @@ -253,7 +254,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 25b46f8e55..34a85710c1 100644 --- a/indra/newview/llvoicechannel.cpp +++ b/indra/newview/llvoicechannel.cpp @@ -891,9 +891,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 573fab1f4f..074f9b8bba 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 91353281a8..d44b47707e 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, "VoiceEffectEnabled")), +	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 e08fed7ae9..5caf26c492 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 new_effects) = 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 recordPreviewBuffer(bool enable) = 0; +	virtual void playPreviewBuffer(bool enable, const LLUUID& effect_id = LLUUID::null) = 0; +	virtual void clearPreviewBuffer() = 0; + +	virtual bool isPreviewRecording() = 0; +	virtual bool isPreviewReady() = 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 bcb1a70efb..7daf865151 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -67,15 +67,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; @@ -101,14 +97,6 @@ const int MAX_LOGIN_RETRIES = 12;  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); -} -  static int scale_mic_volume(float volume)  {  	// incoming volume has the range [0.0 ... 2.0], with 1.0 as the default.                                                 @@ -325,6 +313,7 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() :  	mBuddyListMapPopulated(false),  	mBlockRulesListReceived(false),  	mAutoAcceptRulesListReceived(false), +  	mCaptureDeviceDirty(false),  	mRenderDeviceDirty(false),  	mSpatialCoordsDirty(false), @@ -348,10 +337,13 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() :  	mVoiceEnabled(false),  	mWriteInProgress(false), -	mLipSyncEnabled(false) - +	mLipSyncEnabled(false), +	mVoiceFontsReceived(false), +	mVoiceFontsNew(false), +	mCaptureBufferRecording(false), +	mCaptureBufferPlaying(false)  {	  	mSpeakerVolume = scale_speaker_volume(0); @@ -654,6 +646,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 +659,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 +774,10 @@ void LLVivoxVoiceClient::stateMachine()  			// Clean up and reset everything.   			closeSocket();  			deleteAllSessions(); -			deleteAllBuddies();		 -			 +			deleteAllBuddies(); +			deleteVoiceFonts(); +			deleteVoiceFontTemplates(); +  			mConnectorHandle.clear();  			mAccountHandle.clear();  			mAccountPassword.clear(); @@ -1126,8 +1127,62 @@ void LLVivoxVoiceClient::stateMachine()  		}  		break; -												 -		//MARK: stateConnectorStart + +		//MARK: stateCaptureBufferPaused +		case stateCaptureBufferPaused: +			if (mCaptureBufferRecording) +			{ +				setState(stateCaptureBufferRecStart); +				// Update UI, should really be separated from the VoiceFont callback +				notifyVoiceFontObservers(); +			} +			else if (mCaptureBufferPlaying) +			{ +				setState(stateCaptureBufferPlayStart); +				notifyVoiceFontObservers(); +			} +			else if (mCaptureBufferClear) +			{ +				mCaptureBufferClear = false; +				setState(stateNoChannel); +			} +		break; + +		//MARK: stateCaptureBufferRecStart +		case stateCaptureBufferRecStart: +			captureBufferRecordStartSendMessage(); +			setState(stateCaptureBufferRecording); +		break; + +		//MARK: stateCaptureBufferRecording +		case stateCaptureBufferRecording: +			if (!mCaptureBufferRecording || mCaptureBufferPlaying || mCaptureBufferClear) +			{ +				mCaptureBufferRecording = false; +				captureBufferRecordStopSendMessage(); +				setState(stateCaptureBufferPaused); +				notifyVoiceFontObservers(); +			} +		break; + +		//MARK: stateCaptureBufferPlayStart +		case stateCaptureBufferPlayStart: +			captureBufferPlayStartSendMessage(mPreviewVoiceFontID); +			setState(stateCaptureBufferPlaying); +		break; + +		//MARK: stateCaptureBufferPlaying +		case stateCaptureBufferPlaying: +			if (!mCaptureBufferPlaying || mCaptureBufferRecording || mCaptureBufferClear) +			{ +				mCaptureBufferPlaying = false; +				captureBufferPlayStopSendMessage(); +				setState(stateCaptureBufferPaused); +				notifyVoiceFontObservers(); +			} +		break; + +			//MARK: stateConnectorStart  		case stateConnectorStart:  			if(!mVoiceEnabled)  			{ @@ -1222,6 +1277,18 @@ void LLVivoxVoiceClient::stateMachine()  			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); +			// *FIX: Remove VoiceFontsAvailable temporary setting (only used to test UI behaviour with no fonts) +			if (LLVoiceClient::instance().getVoiceEffectEnabled() && gSavedSettings.getBOOL("VoiceFontsAvailable")) +			{ +				// 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 +1320,21 @@ 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  #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 +1382,11 @@ void LLVivoxVoiceClient::stateMachine()  				mTuningExitState = stateNoChannel;  				setState(stateMicTuningStart);  			} +			else if(mCaptureBufferRecording) +			{ +				mTuningExitState = stateNoChannel; +				setState(stateCaptureBufferRecStart); +			}  			else if(sessionNeedsRelog(mNextAudioSession))  			{  				requestRelog(); @@ -1316,6 +1397,7 @@ void LLVivoxVoiceClient::stateMachine()  				sessionState *oldSession = mAudioSession;  				mAudioSession = mNextAudioSession; +				mAudioSessionChanged = true;  				if(!mAudioSession->mReconnect)	  				{  					mNextAudioSession = NULL; @@ -1547,6 +1629,8 @@ void LLVivoxVoiceClient::stateMachine()  			mAccountHandle.clear();  			deleteAllSessions();  			deleteAllBuddies(); +			deleteVoiceFonts(); +			deleteVoiceFontTemplates();  			if(mVoiceEnabled && !mRelogRequested)  			{ @@ -1627,15 +1711,15 @@ void LLVivoxVoiceClient::stateMachine()  	} -	if(mAudioSession && mAudioSession->mParticipantsChanged) +	if (mAudioSessionChanged)  	{ -		mAudioSession->mParticipantsChanged = false; -		mAudioSessionChanged = true; +		mAudioSessionChanged = false; +		notifyParticipantObservers(); +		notifyVoiceFontObservers();  	} -	 -	if(mAudioSessionChanged) +	else if (mAudioSession && mAudioSession->mParticipantsChanged)  	{ -		mAudioSessionChanged = false; +		mAudioSession->mParticipantsChanged = false;  		notifyParticipantObservers();  	}  } @@ -1751,8 +1835,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 +1863,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 +1875,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 +1905,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 +1916,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 +1929,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 +3252,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 +3530,22 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent(  	}  } +void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType) +{ +	if (mediaCompletionType == "AuxBufferAudioCapture") +	{ +		mCaptureBufferRecording = false; +	} +	else if (mediaCompletionType == "AuxBufferAudioRender") +	{ +		mCaptureBufferPlaying = false; +	} +	else +	{ +		LL_DEBUGS("Voice") << "Unknown MediaCompletionType: " << mediaCompletionType << LL_ENDL; +	} +} +  void LLVivoxVoiceClient::mediaStreamUpdatedEvent(  	std::string &sessionHandle,   	std::string &sessionGroupHandle,  @@ -4134,8 +4246,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 +4742,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 +5762,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 +6192,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 +6356,499 @@ 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(); + +	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["expired"] = font->mHasExpired; +	sd["expiry_date"] = font->mExpirationDate; +	sd["is_new"] = font->mIsNew; + +	return sd; +} + +LLVivoxVoiceClient::voiceFontEntry::voiceFontEntry(LLUUID& id) : +	mID(id), +	mFontIndex(0), +	mHasExpired(false), +	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; +		deleteVoiceFonts(); +		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 std::string &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 and create an entry for it if not. +	voice_font_map_t::iterator iter = font_map.find(font_id); +	bool new_font = (iter == font_map.end()); +	if (new_font) +	{ +		font = new voiceFontEntry(font_id); +	} +	else +	{ +		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->mHasExpired = has_expired; +		font->mFontType = font_type; +		font->mFontStatus = font_status; + +		 // 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; +		} + +		LL_DEBUGS("Voice") << (template_font?"Template: ":"") << font_id +			<< " (" << font_index << ") : " << name << (has_expired?" (Expired)":"") +			<< 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; +		} + +		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)); +		} +	} +} + +void LLVivoxVoiceClient::deleteVoiceFonts() +{ +	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(mVoiceFontsNew); +	mVoiceFontsNew = false; +} + +void LLVivoxVoiceClient::accountGetTemplateFontsResponse(int statusCode, const std::string &statusString) +{ +	// Voice font list entries were updated via addVoiceFont() during parsing. +	notifyVoiceFontObservers(); +} +void LLVivoxVoiceClient::addObserver(LLVoiceEffectObserver* observer) +{ +	mVoiceFontObservers.insert(observer); +} + +void LLVivoxVoiceClient::removeObserver(LLVoiceEffectObserver* observer) +{ +	mVoiceFontObservers.erase(observer); +} + +void LLVivoxVoiceClient::notifyVoiceFontObservers(bool new_fonts) +{ +	for (voice_font_observer_set_t::iterator it = mVoiceFontObservers.begin(); +		 it != mVoiceFontObservers.end(); +		 ) +	{ +		LLVoiceEffectObserver* observer = *it; +		observer->onVoiceEffectChanged(new_fonts); +		// In case onVoiceEffectChanged() deleted an entry. +		it = mVoiceFontObservers.upper_bound(observer); +	} +} + +void LLVivoxVoiceClient::recordPreviewBuffer(bool enable) +{ +	if (enable) +	{ +		mCaptureBufferRecording = true; +		LL_DEBUGS("Voice") << "Starting recording" << LL_ENDL; +		if(getState() >= stateNoChannel) +		{ +			LL_DEBUGS("Voice") << "no channel" << LL_ENDL; +			sessionTerminate(); +		} +	} +	else +	{ +		mCaptureBufferRecording = false; +	} +} + +void LLVivoxVoiceClient::playPreviewBuffer(bool enable, const LLUUID& effect_id) +{ +	if (enable && !isPreviewReady()) +	{ +		LL_DEBUGS("Voice") << "No preview buffer to play" << LL_ENDL; +		return; +	} + +	mCaptureBufferPlaying = enable; +	if (mCaptureBufferPlaying) +	{ +		mPreviewVoiceFontID = effect_id; +	} +} + +void LLVivoxVoiceClient::clearPreviewBuffer() +{ +	mCaptureBufferClear = true; +} + +bool LLVivoxVoiceClient::isPreviewRecording() +{ +	return mCaptureBufferRecording; +} + +bool LLVivoxVoiceClient::isPreviewReady() +{ +	state preview_state = getState(); +	switch (preview_state) +	{ +		case stateCaptureBufferPaused: +		case stateCaptureBufferRecording: +		case stateCaptureBufferPlaying: +			return true; +			break; +		default: +			return false; +			break; +	} +} + +bool LLVivoxVoiceClient::isPreviewPlaying() +{ +	return 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\">" +			<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>" +		<< "</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()) +	{ +		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 +7067,30 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)  			{  				LLVivoxVoiceClient::getInstance()->deleteAllAutoAcceptRules();  			} -			 +			else if (!stricmp("SessionFont", tag)) +			{ +				id = 0; +				nameString.clear(); +				descriptionString.clear(); +				expirationDateString.clear(); +				hasExpired = false; +				fontType = 0; +				fontStatus = 0; +			} +			else if (!stricmp("TemplateFont", tag)) +			{ +				id = 0; +				nameString.clear(); +				descriptionString.clear(); +				expirationDateString.clear(); +				hasExpired = false; +				fontType = 0; +				fontStatus = 0; +			} +			else if (!stricmp("MediaCompletionType", tag)) +			{ +				mediaCompletionType.clear(); +			}  		}  	}  	responseDepth++; @@ -6603,8 +7236,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, expirationDateString, hasExpired, fontType, fontStatus, false); +		} +		else if (!stricmp("TemplateFont", tag)) +		{ +			LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDateString, 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)) +		{ +			expirationDateString = 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; @@ -6686,7 +7354,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 +7425,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 +7542,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 59fec8b954..5ba8082d56 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 recordPreviewBuffer(bool enable); +	virtual void playPreviewBuffer(bool enable, const LLUUID& effect_id = LLUUID::null); +	virtual void clearPreviewBuffer(); + +	virtual bool isPreviewRecording(); +	virtual bool isPreviewReady(); +	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 std::string &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,76 @@ private:  	typedef std::set<LLFriendObserver*> friend_observer_set_t;  	friend_observer_set_t mFriendObservers;  	void notifyFriendObservers(); + +	// Voice Fonts + +	void deleteVoiceFonts(); +	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 new_fonts = 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; +		std::string mExpirationDate; +		bool		mHasExpired; +		S32			mFontType; +		S32			mFontStatus; +		bool		mIsNew; +	}; + +	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; + +	// Audio capture buffer + +	void captureBufferRecordStartSendMessage(); +	void captureBufferRecordStopSendMessage(); +	void captureBufferPlayStartSendMessage(const LLUUID& voice_font_id = LLUUID::null); +	void captureBufferPlayStopSendMessage(); + +	bool mCaptureBufferRecording; +	bool mCaptureBufferPlaying; +	bool mCaptureBufferClear; + +	LLUUID mPreviewVoiceFontID;  };  /**  @@ -890,7 +1010,13 @@ protected:  	int				numberOfAliases;  	std::string		subscriptionHandle;  	std::string		subscriptionType; -	 +	S32				id; +	std::string		descriptionString; +	std::string		expirationDateString; +	bool			hasExpired; +	S32				fontType; +	S32				fontStatus; +	std::string		mediaCompletionType;  	// Members for processing text between tags  	std::string		textBuffer; @@ -913,5 +1039,3 @@ protected:  #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 5b77f11d71..216766c3a6 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="VoiceEffectEnabled" +           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 0000000000..82f816fec1 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_voice_effect.xml @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + legacy_header_height="18" + can_resize="true" + height="465" + name="voice_effects" + help_topic="voice_effects" + title="VOICE EFFECTS" + 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="get_voice_effects_url"> +    http://secondlife.com/landing/v0icem0rphingt3st +  </string> +  <string name="new_voice_effect"> +    New! +  </string> +  <scroll_list +   bottom_delta="400" +   draw_heading="true" +   follows="all" +   layout="topleft" +   left="0" +   multi_select="false" +   top="20" +   name="voice_effect_list"> +    <scroll_list.columns +     label="Name" +     name="name" +     width="153" /> +    <scroll_list.columns +     label="New" +     name="new" +     width="48" /> +	<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> +<!--  <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"> +    <button +     follows="bottom|left" +     font="SansSerifBigBold" +     height="10" +     image_hover_selected="Activate_Checkmark" +     image_selected="Activate_Checkmark" +     image_unselected="Activate_Checkmark" +     layout="topleft" +     left="10" +     name="activate_btn" +     tool_tip="Activate/Deactivate selected voice effect" +     top="10" +     width="10" +      <button.commit_callback +       function="VoiceEffect.Activate" /> +    </button> +  </panel> --> +  <button +   follows="left|bottom" +   height="23" +   label="Record Sample" +   layout="topleft" +   left="6" +   name="record_btn" +   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.Record" /> +  </button> +  <button +   follows="left|bottom" +   height="23" +   label="Play Preview" +   layout="topleft" +   left_pad="6" +   name="play_btn" +   top_delta="0" +   width="135"> +    <button.commit_callback +     function="VoiceEffect.Play" /> +  </button> +  <button +   follows="left|bottom" +   height="23" +   label="Stop" +   layout="topleft" +   left_delta="0" +   name="play_stop_btn" +   top_delta="0" +   width="135"> +    <button.commit_callback +     function="VoiceEffect.Play" /> +  </button> +</floater> 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 0000000000..dbcb7b64f7 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_voice_effect.xml @@ -0,0 +1,35 @@ +<?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="get_voice_effects_url"> +    http://secondlife.com/landing/v0icem0rphingt3st +  </string> +  <string name="get_voice_effects"> +    Add voice effects  ▶ +  </string> +  <string name="preview_voice_effects"> +    Preview  ▶ +  </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>  | 
