diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h
index d4f1423f00f94d579901f998447932b2646fc84c..8d898378d687a3e300aad9044730c8866b0817d4 100644
--- a/indra/newview/llvoiceclient.h
+++ b/indra/newview/llvoiceclient.h
@@ -301,6 +301,18 @@ public:
 	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;
+	//@}
 };
 
 
diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp
index 2fba8e7b5f0d5b6ca61c2a74f9d3355e8ef5e69c..ee67879d3df6703cf9c5cb23cb10dd0f2a684f83 100644
--- a/indra/newview/llvoicevivox.cpp
+++ b/indra/newview/llvoicevivox.cpp
@@ -340,7 +340,10 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() :
 	mLipSyncEnabled(false),
 
 	mVoiceFontsReceived(false),
-	mVoiceFontsNew(false)
+	mVoiceFontsNew(false),
+
+	mCaptureBufferRecording(false),
+	mCaptureBufferPlaying(false)
 {	
 	mSpeakerVolume = scale_speaker_volume(0);
 
@@ -643,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);
@@ -1119,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)
 			{
@@ -1320,6 +1382,11 @@ void LLVivoxVoiceClient::stateMachine()
 				mTuningExitState = stateNoChannel;
 				setState(stateMicTuningStart);
 			}
+			else if(mCaptureBufferRecording)
+			{
+				mTuningExitState = stateNoChannel;
+				setState(stateCaptureBufferRecStart);
+			}
 			else if(sessionNeedsRelog(mNextAudioSession))
 			{
 				requestRelog();
@@ -6562,6 +6629,163 @@ void LLVivoxVoiceClient::notifyVoiceFontObservers(bool new_fonts)
 	}
 }
 
+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 = getVoiceFontIndex(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()
 {
 	parser = NULL;
@@ -7119,6 +7343,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"))
diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h
index 39db87b6bddc51e7f2fc22de8d601cdb1e3a1456..6c302962a3f6d98258c473d12c1c4e69a2c4b93e 100644
--- a/indra/newview/llvoicevivox.h
+++ b/indra/newview/llvoicevivox.h
@@ -253,6 +253,18 @@ public:
 	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();
+	//@}
+
 	//@}
 
 
@@ -373,6 +385,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
@@ -891,6 +908,19 @@ private:
 
 	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;
 };
 
 /**