/** * @file llvieweraudio.cpp * @brief Audio functions that used to be in viewer.cpp * * $LicenseInfo:firstyear=2002&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$ */ #include "llviewerprecompiledheaders.h" #include "llaudioengine.h" #include "llagent.h" #include "llagentcamera.h" #include "llappviewer.h" #include "lldeferredsounds.h" #include "llvieweraudio.h" #include "llviewercamera.h" #include "llviewercontrol.h" #include "llviewerwindow.h" #include "llvoiceclient.h" #include "llviewermedia.h" #include "llviewerregion.h" #include "llprogressview.h" #include "llcallbacklist.h" #include "llstartup.h" #include "llviewerparcelmgr.h" #include "llparcel.h" #include "llviewermessage.h" #include "llstreamingaudio.h" ///////////////////////////////////////////////////////// const U32 FMODEX_DECODE_BUFFER_SIZE = 1000; // in milliseconds const U32 FMODEX_STREAM_BUFFER_SIZE = 7000; // in milliseconds LLViewerAudio::LLViewerAudio() : mDone(true), mFadeState(FADE_IDLE), mFadeTime(), mIdleListnerActive(false), mForcedTeleportFade(false), mWasPlaying(false) { mTeleportFailedConnection = LLViewerParcelMgr::getInstance()-> setTeleportFailedCallback(boost::bind(&LLViewerAudio::onTeleportFailed, this)); mTeleportFinishedConnection = LLViewerParcelMgr::getInstance()-> setTeleportFinishedCallback(boost::bind(&LLViewerAudio::onTeleportFinished, this, _1, _2)); mTeleportStartedConnection = LLViewerMessage::getInstance()-> setTeleportStartedCallback(boost::bind(&LLViewerAudio::onTeleportStarted, this)); } LLViewerAudio::~LLViewerAudio() { mTeleportFailedConnection.disconnect(); mTeleportFinishedConnection.disconnect(); mTeleportStartedConnection.disconnect(); } void LLViewerAudio::registerIdleListener() { if (!mIdleListnerActive) { mIdleListnerActive = true; doOnIdleRepeating(boost::bind(boost::bind(&LLViewerAudio::onIdleUpdate, this))); } } void LLViewerAudio::startInternetStreamWithAutoFade(const std::string &streamURI) { LL_DEBUGS("AudioEngine") << "Start with outo fade: " << streamURI << LL_ENDL; // Old and new stream are identical if (mNextStreamURI == streamURI) { return; } if (!gAudiop) { LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; return; } // Record the URI we are going to be switching to mNextStreamURI = streamURI; switch (mFadeState) { case FADE_IDLE: // If a stream is playing fade it out first if (!gAudiop->getInternetStreamURL().empty()) { // The order of these tests is important, state FADE_OUT will be processed below mFadeState = FADE_OUT; } // Otherwise the new stream can be faded in else { mFadeState = FADE_IN; LLStreamingAudioInterface *stream = gAudiop->getStreamingAudioImpl(); if (stream && stream->supportsAdjustableBufferSizes()) stream->setBufferSizes(FMODEX_STREAM_BUFFER_SIZE, FMODEX_DECODE_BUFFER_SIZE); gAudiop->startInternetStream(mNextStreamURI); } startFading(); break; case FADE_OUT: startFading(); break; case FADE_IN: break; default: LL_WARNS() << "Unknown fading state: " << mFadeState << LL_ENDL; return; } registerIdleListener(); } // A return of false from onIdleUpdate means it will be called again next idle update. // A return of true means we have finished with it and the callback will be deleted. bool LLViewerAudio::onIdleUpdate() { bool fadeIsFinished = false; // There is a delay in the login sequence between when the parcel information has // arrived and the music stream is started and when the audio system is called to set // initial volume levels. This code extends the fade time so you hear a full fade in. if ((LLStartUp::getStartupState() < STATE_STARTED)) { stream_fade_timer.reset(); stream_fade_timer.setTimerExpirySec(mFadeTime); } if (mDone) { // This should be a rare or never occurring state. if (mFadeState == FADE_IDLE) { deregisterIdleListener(); fadeIsFinished = true; // Stop calling onIdleUpdate } // we have finished the current fade operation if (mFadeState == FADE_OUT) { if (gAudiop) { // Clear URI LL_DEBUGS("AudioEngine") << "Done with audio fade" << LL_ENDL; gAudiop->startInternetStream(LLStringUtil::null); gAudiop->stopInternetStream(); } if (!mNextStreamURI.empty()) { mFadeState = FADE_IN; if (gAudiop) { LL_DEBUGS("AudioEngine") << "Audio fade in: " << mNextStreamURI << LL_ENDL; LLStreamingAudioInterface *stream = gAudiop->getStreamingAudioImpl(); if(stream && stream->supportsAdjustableBufferSizes()) stream->setBufferSizes(FMODEX_STREAM_BUFFER_SIZE, FMODEX_DECODE_BUFFER_SIZE); gAudiop->startInternetStream(mNextStreamURI); } startFading(); } else { mFadeState = FADE_IDLE; deregisterIdleListener(); fadeIsFinished = true; // Stop calling onIdleUpdate } } else if (mFadeState == FADE_IN) { if (gAudiop && mNextStreamURI != gAudiop->getInternetStreamURL()) { mFadeState = FADE_OUT; startFading(); } else { mFadeState = FADE_IDLE; deregisterIdleListener(); fadeIsFinished = true; // Stop calling onIdleUpdate } } } return fadeIsFinished; } void LLViewerAudio::stopInternetStreamWithAutoFade() { mFadeState = FADE_IDLE; mNextStreamURI = LLStringUtil::null; mDone = true; if (gAudiop) { LL_DEBUGS("AudioEngine") << "Stop audio fade" << LL_ENDL; gAudiop->startInternetStream(LLStringUtil::null); gAudiop->stopInternetStream(); } } void LLViewerAudio::startFading() { const F32 AUDIO_MUSIC_FADE_IN_TIME = 3.0f; const F32 AUDIO_MUSIC_FADE_OUT_TIME = 2.0f; // This minimum fade time prevents divide by zero and negative times const F32 AUDIO_MUSIC_MINIMUM_FADE_TIME = 0.01f; if (mDone) { // The fade state here should only be one of FADE_IN or FADE_OUT, but, in case it is not, // rather than check for both states assume a fade in and check for the fade out case. mFadeTime = LLViewerAudio::getInstance()->getFadeState() == LLViewerAudio::FADE_OUT ? AUDIO_MUSIC_FADE_OUT_TIME : AUDIO_MUSIC_FADE_IN_TIME; // Prevent invalid fade time mFadeTime = llmax(mFadeTime, AUDIO_MUSIC_MINIMUM_FADE_TIME); stream_fade_timer.reset(); stream_fade_timer.setTimerExpirySec(mFadeTime); mDone = false; } } F32 LLViewerAudio::getFadeVolume() { F32 fade_volume = 1.0f; if (stream_fade_timer.hasExpired()) { mDone = true; // If we have been fading out set volume to 0 until the next fade state occurs to prevent // an audio transient. if (LLViewerAudio::getInstance()->getFadeState() == LLViewerAudio::FADE_OUT) { fade_volume = 0.0f; } } if (!mDone) { // Calculate how far we are into the fade time fade_volume = stream_fade_timer.getElapsedTimeF32() / mFadeTime; if (LLViewerAudio::getInstance()->getFadeState() == LLViewerAudio::FADE_OUT) { // If we are not fading in then we are fading out, so invert the fade // direction; start loud and move towards zero volume. fade_volume = 1.0f - fade_volume; } } return fade_volume; } void LLViewerAudio::onTeleportStarted() { if (gAudiop && !LLViewerAudio::getInstance()->getForcedTeleportFade()) { // Even though the music was turned off it was starting up (with autoplay disabled) occasionally // after a failed teleport or after an intra-parcel teleport. Also, the music sometimes was not // restarting after a successful intra-parcel teleport. Setting mWasPlaying fixes these issues. LLViewerAudio::getInstance()->setWasPlaying(!gAudiop->getInternetStreamURL().empty()); LLViewerAudio::getInstance()->setForcedTeleportFade(true); LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLStringUtil::null); LLViewerAudio::getInstance()->setNextStreamURI(LLStringUtil::null); } } void LLViewerAudio::onTeleportFailed() { // Calling audio_update_volume makes sure that the music stream is properly set to be restored to // its previous value audio_update_volume(false); if (gAudiop && mWasPlaying) { LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); if (parcel) { mNextStreamURI = parcel->getMusicURL(); LL_INFOS() << "Teleport failed -- setting music stream to " << mNextStreamURI << LL_ENDL; } } mWasPlaying = false; } void LLViewerAudio::onTeleportFinished(const LLVector3d& pos, const bool& local) { // Calling audio_update_volume makes sure that the music stream is properly set to be restored to // its previous value audio_update_volume(false); if (gAudiop && local && mWasPlaying) { LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); if (parcel) { mNextStreamURI = parcel->getMusicURL(); LL_INFOS() << "Intraparcel teleport -- setting music stream to " << mNextStreamURI << LL_ENDL; } } mWasPlaying = false; } void init_audio() { if (!gAudiop) { LL_WARNS() << "Failed to create an appropriate Audio Engine" << LL_ENDL; return; } LLVector3d lpos_global = gAgentCamera.getCameraPositionGlobal(); LLVector3 lpos_global_f; lpos_global_f.setVec(lpos_global); gAudiop->setListener(lpos_global_f, LLVector3::zero, // LLViewerCamera::getInstance()->getVelocity(), // !!! BUG need to replace this with smoothed velocity! LLViewerCamera::getInstance()->getUpAxis(), LLViewerCamera::getInstance()->getAtAxis()); // load up our initial set of sounds we'll want so they're in memory and ready to be played bool mute_audio = gSavedSettings.getBOOL("MuteAudio"); if (!mute_audio && false == gSavedSettings.getBOOL("NoPreload")) { gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndAlert"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndBadKeystroke"))); //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndChatFromObject"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndClick"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndClickRelease"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndHealthReductionF"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndHealthReductionM"))); //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndIncomingChat"))); //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndIncomingIM"))); //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInvApplyToObject"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInvalidOp"))); //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInventoryCopyToInv"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndMoneyChangeDown"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndMoneyChangeUp"))); //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectCopyToInv"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectCreate"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectDelete"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectRezIn"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectRezOut"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndSnapshot"))); //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartAutopilot"))); //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartFollowpilot"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartIM"))); //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStopAutopilot"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTeleportOut"))); //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTextureApplyToObject"))); //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTextureCopyToInv"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTyping"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowClose"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowOpen"))); gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndRestart"))); } audio_update_volume(true); } void audio_update_volume(bool force_update) { F32 master_volume = gSavedSettings.getF32("AudioLevelMaster"); bool mute_audio = gSavedSettings.getBOOL("MuteAudio"); LLProgressView* progress = gViewerWindow->getProgressView(); bool progress_view_visible = false; if (progress) { progress_view_visible = progress->getVisible(); } if (!gViewerWindow->getActive() && gSavedSettings.getBOOL("MuteWhenMinimized")) { mute_audio = true; } F32 mute_volume = mute_audio ? 0.0f : 1.0f; if (gAudiop) { // Sound Effects gAudiop->setMasterGain ( master_volume ); const F32 AUDIO_LEVEL_DOPPLER = 1.f; gAudiop->setDopplerFactor(AUDIO_LEVEL_DOPPLER); if(!LLViewerCamera::getInstance()->cameraUnderWater()) { const F32 AUDIO_LEVEL_ROLLOFF = 1.f; gAudiop->setRolloffFactor(AUDIO_LEVEL_ROLLOFF); } else { const F32 AUDIO_LEVEL_UNDERWATER_ROLLOFF = 5.f; gAudiop->setRolloffFactor(AUDIO_LEVEL_UNDERWATER_ROLLOFF); } gAudiop->setMuted(mute_audio || progress_view_visible); //Play any deferred sounds when unmuted if(!gAudiop->getMuted()) { LLDeferredSounds::instance().playdeferredSounds(); } if (force_update) { audio_update_wind(true); } // handle secondary gains gAudiop->setSecondaryGain(LLAudioEngine::AUDIO_TYPE_SFX, gSavedSettings.getBOOL("MuteSounds") ? 0.f : gSavedSettings.getF32("AudioLevelSFX")); gAudiop->setSecondaryGain(LLAudioEngine::AUDIO_TYPE_UI, gSavedSettings.getBOOL("MuteUI") ? 0.f : gSavedSettings.getF32("AudioLevelUI")); gAudiop->setSecondaryGain(LLAudioEngine::AUDIO_TYPE_AMBIENT, gSavedSettings.getBOOL("MuteAmbient") ? 0.f : gSavedSettings.getF32("AudioLevelAmbient")); // Streaming Music if (!progress_view_visible && LLViewerAudio::getInstance()->getForcedTeleportFade()) { LLViewerAudio::getInstance()->setWasPlaying(!gAudiop->getInternetStreamURL().empty()); LLViewerAudio::getInstance()->setForcedTeleportFade(false); } F32 music_volume = gSavedSettings.getF32("AudioLevelMusic"); bool music_muted = gSavedSettings.getBOOL("MuteMusic"); F32 fade_volume = LLViewerAudio::getInstance()->getFadeVolume(); music_volume = mute_volume * master_volume * music_volume * fade_volume; gAudiop->setInternetStreamGain (music_muted ? 0.f : music_volume); } // Streaming Media F32 media_volume = gSavedSettings.getF32("AudioLevelMedia"); bool media_muted = gSavedSettings.getBOOL("MuteMedia"); media_volume = mute_volume * master_volume * media_volume; LLViewerMedia::getInstance()->setVolume( media_muted ? 0.0f : media_volume ); // Voice, this is parametric singleton, it gets initialized when ready if (LLVoiceClient::instanceExists()) { F32 voice_volume = gSavedSettings.getF32("AudioLevelVoice"); voice_volume = mute_volume * master_volume * voice_volume; bool voice_mute = gSavedSettings.getBOOL("MuteVoice"); LLVoiceClient *voice_inst = LLVoiceClient::getInstance(); voice_inst->setVoiceVolume(voice_mute ? 0.f : voice_volume); voice_inst->setMicGain(voice_mute ? 0.f : gSavedSettings.getF32("AudioLevelMic")); if (!gViewerWindow->getActive() && (gSavedSettings.getBOOL("MuteWhenMinimized"))) { voice_inst->setMuteMic(true); } else { voice_inst->setMuteMic(false); } } } void audio_update_listener() { if (gAudiop) { // update listener position because agent has moved static LLUICachedControl mEarLocation("MediaSoundsEarLocation", 0); LLVector3d ear_position; switch(mEarLocation) { case 0: default: ear_position = gAgentCamera.getCameraPositionGlobal(); break; case 1: ear_position = gAgent.getPositionGlobal(); break; } LLVector3d lpos_global = ear_position; LLVector3 lpos_global_f; lpos_global_f.setVec(lpos_global); gAudiop->setListener(lpos_global_f, // LLViewerCamera::getInstance()VelocitySmoothed, // LLVector3::zero, gAgent.getVelocity(), // !!! *TODO: need to replace this with smoothed velocity! LLViewerCamera::getInstance()->getUpAxis(), LLViewerCamera::getInstance()->getAtAxis()); } } void audio_update_wind(bool force_update) { #ifdef kAUDIO_ENABLE_WIND LLViewerRegion* region = gAgent.getRegion(); if (region) { // Scale down the contribution of weather-simulation wind to the // ambient wind noise. Wind velocity averages 3.5 m/s, with gusts to 7 m/s // whereas steady-state avatar walk velocity is only 3.2 m/s. // Without this the world feels desolate on first login when you are // standing still. const F32 WIND_LEVEL = 0.5f; LLVector3 scaled_wind_vec = gWindVec * WIND_LEVEL; // Mix in the avatar's motion, subtract because when you walk north, // the apparent wind moves south. LLVector3 final_wind_vec = scaled_wind_vec - gAgent.getVelocity(); // rotate the wind vector to be listener (agent) relative gRelativeWindVec = gAgent.getFrameAgent().rotateToLocal( final_wind_vec ); // don't use the setter setMaxWindGain() because we don't // want to screw up the fade-in on startup by setting actual source gain // outside the fade-in. static LLCachedControl mute_audio(gSavedSettings, "MuteAudio"); static LLCachedControl mute_ambient(gSavedSettings, "MuteAmbient"); static LLCachedControl level_master(gSavedSettings, "AudioLevelMaster"); static LLCachedControl level_ambient(gSavedSettings, "AudioLevelAmbient"); F32 master_volume = mute_audio() ? 0.f : level_master(); F32 ambient_volume = mute_ambient() ? 0.f : level_ambient(); F32 max_wind_volume = master_volume * ambient_volume; const F32 WIND_SOUND_TRANSITION_TIME = 2.f; // amount to change volume this frame F32 volume_delta = (LLFrameTimer::getFrameDeltaTimeF32() / WIND_SOUND_TRANSITION_TIME) * max_wind_volume; if (force_update) { // initialize wind volume (force_update) by using large volume_delta // which is sufficient to completely turn off or turn on wind noise volume_delta = 1.f; } if (!gAudiop) { LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; return; } // mute wind when not flying if (gAgent.getFlying()) { // volume increases by volume_delta, up to no more than max_wind_volume gAudiop->mMaxWindGain = llmin(gAudiop->mMaxWindGain + volume_delta, max_wind_volume); } else { // volume decreases by volume_delta, down to no less than 0 gAudiop->mMaxWindGain = llmax(gAudiop->mMaxWindGain - volume_delta, 0.f); } gAudiop->updateWind(gRelativeWindVec, gAgentCamera.getCameraPositionAgent()[VZ] - gAgent.getRegion()->getWaterHeight()); } #endif }