/** * @file llvoicevisualizer.cpp * @brief Draws in-world speaking indicators. * * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ //---------------------------------------------------------------------- // Voice Visualizer // author: JJ Ventrella // (information about this stuff can be found in "llvoicevisualizer.h") //---------------------------------------------------------------------- #include "llviewerprecompiledheaders.h" #include "llviewercontrol.h" #include "llglheaders.h" #include "llsphere.h" #include "llvoicevisualizer.h" #include "llviewercamera.h" #include "llviewerobject.h" #include "llviewertexture.h" #include "llviewertexturelist.h" #include "llvoiceclient.h" #include "llrender.h" //brent's wave image //29de489d-0491-fb00-7dab-f9e686d31e83 //-------------------------------------------------------------------------------------- // sound symbol constants //-------------------------------------------------------------------------------------- const F32 HEIGHT_ABOVE_HEAD = 0.3f; // how many meters vertically above the av's head the voice symbol will appear const F32 RED_THRESHOLD = LLVoiceClient::OVERDRIVEN_POWER_LEVEL; // value above which speaking amplitude causes the voice symbol to turn red const F32 GREEN_THRESHOLD = 0.2f; // value above which speaking amplitude causes the voice symbol to turn green const F32 FADE_OUT_DURATION = 0.4f; // how many seconds it takes for a pair of waves to fade away const F32 EXPANSION_RATE = 1.0f; // how many seconds it takes for the waves to expand to twice their original size const F32 EXPANSION_MAX = 1.5f; // maximum size scale to which the waves can expand before popping back to 1.0 const F32 WAVE_WIDTH_SCALE = 0.03f; // base width of the waves const F32 WAVE_HEIGHT_SCALE = 0.02f; // base height of the waves const F32 BASE_BRIGHTNESS = 0.7f; // gray level of the voice indicator when quiet (below green threshold) const F32 DOT_SIZE = 0.05f; // size of the dot billboard texture const F32 DOT_OPACITY = 0.7f; // how opaque the dot is const F32 WAVE_MOTION_RATE = 1.5f; // scalar applied to consecutive waves as a function of speaking amplitude //-------------------------------------------------------------------------------------- // gesticulation constants //-------------------------------------------------------------------------------------- const F32 DEFAULT_MINIMUM_GESTICULATION_AMPLITUDE = 0.2f; const F32 DEFAULT_MAXIMUM_GESTICULATION_AMPLITUDE = 1.0f; //-------------------------------------------------------------------------------------- // other constants //-------------------------------------------------------------------------------------- const F32 ONE_HALF = 1.0f; // to clarify intent and reduce magic numbers in the code. const LLVector3 WORLD_UPWARD_DIRECTION = LLVector3( 0.0f, 0.0f, 1.0f ); // Z is up in SL //------------------------------------------------------------------ // handles parameter updates //------------------------------------------------------------------ static bool handleVoiceVisualizerPrefsChanged(const LLSD& newvalue) { // Note: Ignore the specific event value, we look up the ones we want LLVoiceVisualizer::setPreferences(); return true; } //------------------------------------------------------------------ // Initialize the statics //------------------------------------------------------------------ bool LLVoiceVisualizer::sPrefsInitialized = false; BOOL LLVoiceVisualizer::sLipSyncEnabled = FALSE; F32* LLVoiceVisualizer::sOoh = NULL; F32* LLVoiceVisualizer::sAah = NULL; U32 LLVoiceVisualizer::sOohs = 0; U32 LLVoiceVisualizer::sAahs = 0; F32 LLVoiceVisualizer::sOohAahRate = 0.0f; F32* LLVoiceVisualizer::sOohPowerTransfer = NULL; U32 LLVoiceVisualizer::sOohPowerTransfers = 0; F32 LLVoiceVisualizer::sOohPowerTransfersf = 0.0f; F32* LLVoiceVisualizer::sAahPowerTransfer = NULL; U32 LLVoiceVisualizer::sAahPowerTransfers = 0; F32 LLVoiceVisualizer::sAahPowerTransfersf = 0.0f; //----------------------------------------------- // constructor //----------------------------------------------- LLVoiceVisualizer::LLVoiceVisualizer( const U8 type ) :LLHUDEffect( type ) { mCurrentTime = mTimer.getTotalSeconds(); mPreviousTime = mCurrentTime; mStartTime = mCurrentTime; mVoiceSourceWorldPosition = LLVector3( 0.0f, 0.0f, 0.0f ); mSpeakingAmplitude = 0.0f; mCurrentlySpeaking = false; mVoiceEnabled = false; mMinGesticulationAmplitude = DEFAULT_MINIMUM_GESTICULATION_AMPLITUDE; mMaxGesticulationAmplitude = DEFAULT_MAXIMUM_GESTICULATION_AMPLITUDE; mSoundSymbol.mActive = true; mSoundSymbol.mPosition = LLVector3( 0.0f, 0.0f, 0.0f ); mTimer.reset(); const char* sound_level_img[] = { "voice_meter_dot.j2c", "voice_meter_rings.j2c", "voice_meter_rings.j2c", "voice_meter_rings.j2c", "voice_meter_rings.j2c", "voice_meter_rings.j2c", "voice_meter_rings.j2c" }; for (int i=0; i<NUM_VOICE_SYMBOL_WAVES; i++) { mSoundSymbol.mWaveFadeOutStartTime [i] = mCurrentTime; mSoundSymbol.mTexture [i] = LLViewerTextureManager::getFetchedTextureFromFile(sound_level_img[i], FALSE, LLViewerTexture::BOOST_UI); mSoundSymbol.mWaveActive [i] = false; mSoundSymbol.mWaveOpacity [i] = 1.0f; mSoundSymbol.mWaveExpansion [i] = 1.0f; } mSoundSymbol.mTexture[0]->setFilteringOption(LLTexUnit::TFO_ANISOTROPIC); // The first instance loads the initial state from prefs. if (!sPrefsInitialized) { setPreferences(); // Set up our listener to get updates on all prefs values we care about. gSavedSettings.getControl("LipSyncEnabled")->getSignal()->connect(boost::bind(&handleVoiceVisualizerPrefsChanged, _2)); gSavedSettings.getControl("LipSyncOohAahRate")->getSignal()->connect(boost::bind(&handleVoiceVisualizerPrefsChanged, _2)); gSavedSettings.getControl("LipSyncOoh")->getSignal()->connect(boost::bind(&handleVoiceVisualizerPrefsChanged, _2)); gSavedSettings.getControl("LipSyncAah")->getSignal()->connect(boost::bind(&handleVoiceVisualizerPrefsChanged, _2)); gSavedSettings.getControl("LipSyncOohPowerTransfer")->getSignal()->connect(boost::bind(&handleVoiceVisualizerPrefsChanged, _2)); gSavedSettings.getControl("LipSyncAahPowerTransfer")->getSignal()->connect(boost::bind(&handleVoiceVisualizerPrefsChanged, _2)); sPrefsInitialized = true; } }//--------------------------------------------------- //--------------------------------------------------- void LLVoiceVisualizer::setMinGesticulationAmplitude( F32 m ) { mMinGesticulationAmplitude = m; }//--------------------------------------------------- //--------------------------------------------------- void LLVoiceVisualizer::setMaxGesticulationAmplitude( F32 m ) { mMaxGesticulationAmplitude = m; }//--------------------------------------------------- //--------------------------------------------------- void LLVoiceVisualizer::setVoiceEnabled( bool v ) { mVoiceEnabled = v; }//--------------------------------------------------- //--------------------------------------------------- void LLVoiceVisualizer::setStartSpeaking() { mStartTime = mTimer.getTotalSeconds(); mCurrentlySpeaking = true; mSoundSymbol.mActive = true; }//--------------------------------------------------- //--------------------------------------------------- bool LLVoiceVisualizer::getCurrentlySpeaking() { return mCurrentlySpeaking; }//--------------------------------------------------- //--------------------------------------------------- void LLVoiceVisualizer::setStopSpeaking() { mCurrentlySpeaking = false; mSpeakingAmplitude = 0.0f; }//--------------------------------------------------- //--------------------------------------------------- void LLVoiceVisualizer::setSpeakingAmplitude( F32 a ) { mSpeakingAmplitude = a; }//--------------------------------------------------- //--------------------------------------------------- void LLVoiceVisualizer::setPreferences( ) { sLipSyncEnabled = gSavedSettings.getBOOL("LipSyncEnabled"); sOohAahRate = gSavedSettings.getF32("LipSyncOohAahRate"); std::string oohString = gSavedSettings.getString("LipSyncOoh"); lipStringToF32s (oohString, sOoh, sOohs); std::string aahString = gSavedSettings.getString("LipSyncAah"); lipStringToF32s (aahString, sAah, sAahs); std::string oohPowerString = gSavedSettings.getString("LipSyncOohPowerTransfer"); lipStringToF32s (oohPowerString, sOohPowerTransfer, sOohPowerTransfers); sOohPowerTransfersf = (F32) sOohPowerTransfers; std::string aahPowerString = gSavedSettings.getString("LipSyncAahPowerTransfer"); lipStringToF32s (aahPowerString, sAahPowerTransfer, sAahPowerTransfers); sAahPowerTransfersf = (F32) sAahPowerTransfers; }//--------------------------------------------------- //--------------------------------------------------- // convert a string of digits to an array of floats. // the result for each digit is the value of the // digit multiplied by 0.11 //--------------------------------------------------- void LLVoiceVisualizer::lipStringToF32s ( std::string& in_string, F32*& out_F32s, U32& count_F32s ) { delete[] out_F32s; // get rid of the current array count_F32s = in_string.length(); if (count_F32s == 0) { // we don't like zero length arrays count_F32s = 1; out_F32s = new F32[1]; out_F32s[0] = 0.0f; } else { out_F32s = new F32[count_F32s]; for (U32 i=0; i<count_F32s; i++) { // we convert the characters 0 to 9 to their numeric value // anything else we take the low order four bits with a ceiling of 9 U8 digit = in_string[i]; U8 four_bits = digit % 16; if (four_bits > 9) { four_bits = 9; } out_F32s[i] = 0.11f * (F32) four_bits; } } }//--------------------------------------------------- //-------------------------------------------------------------------------- // find the amount to blend the ooh and aah mouth morphs //-------------------------------------------------------------------------- void LLVoiceVisualizer::lipSyncOohAah( F32& ooh, F32& aah ) { if( ( sLipSyncEnabled == TRUE ) && mCurrentlySpeaking ) { U32 transfer_index = (U32) (sOohPowerTransfersf * mSpeakingAmplitude); if (transfer_index >= sOohPowerTransfers) { transfer_index = sOohPowerTransfers - 1; } F32 transfer_ooh = sOohPowerTransfer[transfer_index]; transfer_index = (U32) (sAahPowerTransfersf * mSpeakingAmplitude); if (transfer_index >= sAahPowerTransfers) { transfer_index = sAahPowerTransfers - 1; } F32 transfer_aah = sAahPowerTransfer[transfer_index]; F64 current_time = mTimer.getTotalSeconds(); F64 elapsed_time = current_time - mStartTime; U32 elapsed_frames = (U32) (elapsed_time * sOohAahRate); U32 elapsed_oohs = elapsed_frames % sOohs; U32 elapsed_aahs = elapsed_frames % sAahs; ooh = transfer_ooh * sOoh[elapsed_oohs]; aah = transfer_aah * sAah[elapsed_aahs]; /* llinfos << " elapsed frames " << elapsed_frames << " ooh " << ooh << " aah " << aah << " transfer ooh" << transfer_ooh << " transfer aah" << transfer_aah << " start time " << mStartTime << " current time " << current_time << " elapsed time " << elapsed_time << " elapsed oohs " << elapsed_oohs << " elapsed aahs " << elapsed_aahs << llendl; */ } else { ooh = 0.0f; aah = 0.0f; } }//--------------------------------------------------- //--------------------------------------------------- // this method is inherited from HUD Effect //--------------------------------------------------- void LLVoiceVisualizer::render() { if ( ! mVoiceEnabled ) { return; } if ( mSoundSymbol.mActive ) { mPreviousTime = mCurrentTime; mCurrentTime = mTimer.getTotalSeconds(); //--------------------------------------------------------------- // set the sound symbol position over the source (avatar's head) //--------------------------------------------------------------- mSoundSymbol.mPosition = mVoiceSourceWorldPosition + WORLD_UPWARD_DIRECTION * HEIGHT_ABOVE_HEAD; //--------------------------------------------------------------- // some gl state //--------------------------------------------------------------- LLGLSPipelineAlpha alpha_blend; LLGLDepthTest depth(GL_TRUE, GL_FALSE); //------------------------------------------------------------- // create coordinates of the geometry for the dot //------------------------------------------------------------- LLViewerCamera* camera = LLViewerCamera::getInstance(); LLVector3 l = camera->getLeftAxis() * DOT_SIZE; LLVector3 u = camera->getUpAxis() * DOT_SIZE; LLVector3 bottomLeft = mSoundSymbol.mPosition + l - u; LLVector3 bottomRight = mSoundSymbol.mPosition - l - u; LLVector3 topLeft = mSoundSymbol.mPosition + l + u; LLVector3 topRight = mSoundSymbol.mPosition - l + u; //----------------------------- // bind texture 0 (the dot) //----------------------------- gGL.getTexUnit(0)->bind(mSoundSymbol.mTexture[0]); //------------------------------------------------------------- // now render the dot //------------------------------------------------------------- gGL.color4fv( LLColor4( 1.0f, 1.0f, 1.0f, DOT_OPACITY ).mV ); gGL.begin( LLRender::TRIANGLE_STRIP ); gGL.texCoord2i( 0, 0 ); gGL.vertex3fv( bottomLeft.mV ); gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); gGL.end(); gGL.begin( LLRender::TRIANGLE_STRIP ); gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); gGL.texCoord2i( 1, 1 ); gGL.vertex3fv( topRight.mV ); gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); gGL.end(); //-------------------------------------------------------------------------------------- // if currently speaking, trigger waves (1 through 6) based on speaking amplitude //-------------------------------------------------------------------------------------- if ( mCurrentlySpeaking ) { F32 min = 0.2f; F32 max = 0.7f; F32 fraction = ( mSpeakingAmplitude - min ) / ( max - min ); // in case mSpeakingAmplitude > max.... if ( fraction > 1.0f ) { fraction = 1.0f; } S32 level = 1 + (int)( fraction * ( NUM_VOICE_SYMBOL_WAVES - 2 ) ); for (int i=0; i<level+1; i++) { mSoundSymbol.mWaveActive [i] = true; mSoundSymbol.mWaveOpacity [i] = 1.0f; mSoundSymbol.mWaveFadeOutStartTime [i] = mCurrentTime; } } // if currently speaking //--------------------------------------------------- // determine color //--------------------------------------------------- F32 red = 0.0f; F32 green = 0.0f; F32 blue = 0.0f; if ( mSpeakingAmplitude < RED_THRESHOLD ) { if ( mSpeakingAmplitude < GREEN_THRESHOLD ) { red = BASE_BRIGHTNESS; green = BASE_BRIGHTNESS; blue = BASE_BRIGHTNESS; } else { //--------------------------------------------------- // fade from gray to bright green //--------------------------------------------------- F32 fraction = ( mSpeakingAmplitude - GREEN_THRESHOLD ) / ( 1.0f - GREEN_THRESHOLD ); red = BASE_BRIGHTNESS - ( fraction * BASE_BRIGHTNESS ); green = BASE_BRIGHTNESS + fraction * ( 1.0f - BASE_BRIGHTNESS ); blue = BASE_BRIGHTNESS - ( fraction * BASE_BRIGHTNESS ); } } else { //--------------------------------------------------- // redish //--------------------------------------------------- red = 1.0f; green = 0.2f; blue = 0.2f; } for (int i=0; i<NUM_VOICE_SYMBOL_WAVES; i++) { if ( mSoundSymbol.mWaveActive[i] ) { F32 fadeOutFraction = (F32)( mCurrentTime - mSoundSymbol.mWaveFadeOutStartTime[i] ) / FADE_OUT_DURATION; mSoundSymbol.mWaveOpacity[i] = 1.0f - fadeOutFraction; if ( mSoundSymbol.mWaveOpacity[i] < 0.0f ) { mSoundSymbol.mWaveFadeOutStartTime [i] = mCurrentTime; mSoundSymbol.mWaveOpacity [i] = 0.0f; mSoundSymbol.mWaveActive [i] = false; } //---------------------------------------------------------------------------------- // This is where we calculate the expansion of the waves - that is, the // rate at which they are scaled greater than 1.0 so that they grow over time. //---------------------------------------------------------------------------------- F32 timeSlice = (F32)( mCurrentTime - mPreviousTime ); F32 waveSpeed = mSpeakingAmplitude * WAVE_MOTION_RATE; mSoundSymbol.mWaveExpansion[i] *= ( 1.0f + EXPANSION_RATE * timeSlice * waveSpeed ); if ( mSoundSymbol.mWaveExpansion[i] > EXPANSION_MAX ) { mSoundSymbol.mWaveExpansion[i] = 1.0f; } //---------------------------------------------------------------------------------- // create geometry for the wave billboard textures //---------------------------------------------------------------------------------- F32 width = i * WAVE_WIDTH_SCALE * mSoundSymbol.mWaveExpansion[i]; F32 height = i * WAVE_HEIGHT_SCALE * mSoundSymbol.mWaveExpansion[i]; LLVector3 l = camera->getLeftAxis() * width; LLVector3 u = camera->getUpAxis() * height; LLVector3 bottomLeft = mSoundSymbol.mPosition + l - u; LLVector3 bottomRight = mSoundSymbol.mPosition - l - u; LLVector3 topLeft = mSoundSymbol.mPosition + l + u; LLVector3 topRight = mSoundSymbol.mPosition - l + u; gGL.color4fv( LLColor4( red, green, blue, mSoundSymbol.mWaveOpacity[i] ).mV ); gGL.getTexUnit(0)->bind(mSoundSymbol.mTexture[i]); //--------------------------------------------------- // now, render the mofo //--------------------------------------------------- gGL.begin( LLRender::TRIANGLE_STRIP ); gGL.texCoord2i( 0, 0 ); gGL.vertex3fv( bottomLeft.mV ); gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); gGL.end(); gGL.begin( LLRender::TRIANGLE_STRIP ); gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); gGL.texCoord2i( 1, 1 ); gGL.vertex3fv( topRight.mV ); gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); gGL.end(); } //if ( mSoundSymbol.mWaveActive[i] ) }// for loop }//if ( mSoundSymbol.mActive ) }//--------------------------------------------------- //--------------------------------------------------- void LLVoiceVisualizer::setVoiceSourceWorldPosition( const LLVector3 &p ) { mVoiceSourceWorldPosition = p; }//--------------------------------------------------- //--------------------------------------------------- VoiceGesticulationLevel LLVoiceVisualizer::getCurrentGesticulationLevel() { VoiceGesticulationLevel gesticulationLevel = VOICE_GESTICULATION_LEVEL_OFF; //default //----------------------------------------------------------------------------------------- // Within the range of gesticulation amplitudes, the sound signal is split into // three equal amplitude regimes, each specifying one of three gesticulation levels. //----------------------------------------------------------------------------------------- F32 range = mMaxGesticulationAmplitude - mMinGesticulationAmplitude; if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.5f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_HIGH; } else if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.25f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_MEDIUM; } else if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.00000f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_LOW; } return gesticulationLevel; }//--------------------------------------------------- //------------------------------------ // Destructor //------------------------------------ LLVoiceVisualizer::~LLVoiceVisualizer() { }//---------------------------------------------- //--------------------------------------------------- // "packData" is inherited from HUDEffect //--------------------------------------------------- void LLVoiceVisualizer::packData(LLMessageSystem *mesgsys) { // Pack the default data LLHUDEffect::packData(mesgsys); // TODO -- pack the relevant data for voice effects // we'll come up with some cool configurations....TBD //U8 packed_data[41]; //mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, 41); U8 packed_data = 0; mesgsys->addBinaryDataFast(_PREHASH_TypeData, &packed_data, 1); } //--------------------------------------------------- // "unpackData" is inherited from HUDEffect //--------------------------------------------------- void LLVoiceVisualizer::unpackData(LLMessageSystem *mesgsys, S32 blocknum) { // TODO -- find the speaker, unpack binary data, set the properties of this effect /* LLHUDEffect::unpackData(mesgsys, blocknum); LLUUID source_id; LLUUID target_id; S32 size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); if (size != 1) { llwarns << "Voice effect with bad size " << size << llendl; return; } mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, packed_data, 1, blocknum); */ } //------------------------------------------------------------------ // this method is inherited from HUD Effect //------------------------------------------------------------------ void LLVoiceVisualizer::markDead() { mCurrentlySpeaking = false; mVoiceEnabled = false; mSoundSymbol.mActive = false; LLHUDEffect::markDead(); }//------------------------------------------------------------------