/** 
 * @file audioengine_fmod.cpp
 * @brief Implementation of LLAudioEngine class abstracting the audio
 * support as a FMOD 3D implementation
 *
 * $LicenseInfo:firstyear=2002&license=viewergpl$
 * 
 * Copyright (c) 2002-2008, 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 "linden_common.h"

#include "audioengine_fmod.h"
#include "listener_fmod.h"

#include "llerror.h"
#include "llmath.h"
#include "llrand.h"

#include "fmod.h"
#include "fmod_errors.h"
#include "lldir.h"
#include "llapr.h"

#include "sound_ids.h"


void * F_CALLBACKAPI windCallback(void *originalbuffer, void *newbuffer, int length, void* userdata);
FSOUND_DSPUNIT *gWindDSP = NULL;

// These globals for the wind filter.  Blech!
F64 gbuf0 = 0.0;
F64 gbuf1 = 0.0;
F64 gbuf2 = 0.0;
F64 gbuf3 = 0.0;
F64 gbuf4 = 0.0;
F64 gbuf5 = 0.0;
F64 gY0 = 0.0;
F64 gY1 = 0.0;

F32 gTargetGain = 0.f;
F32 gCurrentGain = 0.f;
F32 gTargetFreq = 100.f;
F32 gCurrentFreq = 100.f;
F32 gTargetPanGainR = 0.5f;
F32 gCurrentPanGainR = 0.5f;


// Safe strcpy
#if 0 //(unused)  //LL_WINDOWS || LL_LINUX
static size_t strlcpy( char* dest, const char* src, size_t dst_size )
{
	size_t source_len = 0;
	size_t min_len = 0;
	if( dst_size > 0 )
	{
		if( src )
		{
			source_len = strlen(src);	/*Flawfinder: ignore*/
			min_len = llmin( dst_size - 1, source_len );
			memcpy(dest, src, min_len);	/*Flawfinder: ignore*/	
		}
		dest[min_len] = '\0';
	}
	return source_len;
}
#else
// apple ships with the non-standard strlcpy in /usr/include/string.h:
// size_t strlcpy(char *, const char *, size_t);
#endif


LLAudioEngine_FMOD::LLAudioEngine_FMOD()
{
	mInited = FALSE;
	mCurrentInternetStreamp = NULL;
	mInternetStreamChannel = -1;
}


LLAudioEngine_FMOD::~LLAudioEngine_FMOD()
{
}


BOOL LLAudioEngine_FMOD::init(const S32 num_channels, void* userdata)
{
	mFadeIn = -10000;

	LLAudioEngine::init(num_channels, userdata);

	// Reserve one extra channel for the http stream.
	if (!FSOUND_SetMinHardwareChannels(num_channels + 1))
	{
		LL_WARNS("AppInit") << "FMOD::init[0](), error: " << FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
	}

	LL_DEBUGS("AppInit") << "LLAudioEngine_FMOD::init() initializing FMOD" << LL_ENDL;

	F32 version = FSOUND_GetVersion();
	if (version < FMOD_VERSION)
	{
		LL_WARNS("AppInit") << "Error : You are using the wrong FMOD version (" << version
			<< ")!  You should be using FMOD " << FMOD_VERSION << LL_ENDL;
		//return FALSE;
	}

	U32 fmod_flags = 0x0;

#if LL_WINDOWS
	// Windows needs to know which window is frontmost.
	// This must be called before FSOUND_Init() per the FMOD docs.
	// This could be used to let FMOD handle muting when we lose focus,
	// but we don't actually want to do that because we want to distinguish
	// between minimized and not-focused states.
	if (!FSOUND_SetHWND(userdata))
	{
		LL_WARNS("AppInit") << "Error setting FMOD window: "
			<< FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
		return FALSE;
	}
	// Play audio when we don't have focus.
	// (For example, IM client on top of us.)
	// This means we also try to play audio when minimized,
	// so we manually handle muting in that case. JC
	fmod_flags |= FSOUND_INIT_GLOBALFOCUS;
#endif

#if LL_LINUX
	// initialize the FMOD engine

	// This is a hack to use only FMOD's basic FPU mixer
	// when the LL_VALGRIND environmental variable is set,
	// otherwise valgrind will fall over on FMOD's MMX detection
	if (getenv("LL_VALGRIND"))		/*Flawfinder: ignore*/
	{
		LL_INFOS("AppInit") << "Pacifying valgrind in FMOD init." << LL_ENDL;
		FSOUND_SetMixer(FSOUND_MIXER_QUALITY_FPU);
	}

	// If we don't set an output method, Linux FMOD always
	// decides on OSS and fails otherwise.  So we'll manually
	// try ESD, then OSS, then ALSA.
	// Why this order?  See SL-13250, but in short, OSS emulated
	// on top of ALSA is ironically more reliable than raw ALSA.
	// Ack, and ESD has more reliable failure modes - but has worse
	// latency - than all of them, so wins for now.
	BOOL audio_ok = FALSE;

	if (!audio_ok)
		if (NULL == getenv("LL_BAD_ESD")) /*Flawfinder: ignore*/
		{
			LL_DEBUGS("AppInit") << "Trying ESD audio output..." << LL_ENDL;
			if(FSOUND_SetOutput(FSOUND_OUTPUT_ESD) &&
			   FSOUND_Init(44100, num_channels, fmod_flags))
			{
				LL_DEBUGS("AppInit") << "ESD audio output initialized OKAY"
					<< LL_ENDL;
				audio_ok = TRUE;
			} else {
				LL_WARNS("AppInit") << "ESD audio output FAILED to initialize: "
					<< FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
			}
		} else {
			LL_DEBUGS("AppInit") << "ESD audio output SKIPPED" << LL_ENDL;
		}

	if (!audio_ok)
		if (NULL == getenv("LL_BAD_OSS")) 	 /*Flawfinder: ignore*/
		{
			LL_DEBUGS("AppInit") << "Trying OSS audio output..."	<< LL_ENDL;
			if(FSOUND_SetOutput(FSOUND_OUTPUT_OSS) &&
			   FSOUND_Init(44100, num_channels, fmod_flags))
			{
				LL_DEBUGS("AppInit") << "OSS audio output initialized OKAY" << LL_ENDL;
				audio_ok = TRUE;
			} else {
				LL_WARNS("AppInit") << "OSS audio output FAILED to initialize: "
					<< FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
			}
		} else {
			LL_DEBUGS("AppInit") << "OSS audio output SKIPPED" << LL_ENDL;
		}

	if (!audio_ok)
		if (NULL == getenv("LL_BAD_ALSA"))		/*Flawfinder: ignore*/
		{
			LL_DEBUGS("AppInit") << "Trying ALSA audio output..." << LL_ENDL;
			if(FSOUND_SetOutput(FSOUND_OUTPUT_ALSA) &&
			   FSOUND_Init(44100, num_channels, fmod_flags))
			{
				LL_DEBUGS("AppInit") << "ALSA audio output initialized OKAY" << LL_ENDL;
				audio_ok = TRUE;
			} else {
				LL_WARNS("AppInit") << "ALSA audio output FAILED to initialize: "
					<< FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
			}
		} else {
			LL_DEBUGS("AppInit") << "OSS audio output SKIPPED" << LL_ENDL;
		}

	if (!audio_ok)
	{
		LL_WARNS("AppInit") << "Overall audio init failure." << LL_ENDL;
		return FALSE;
	}

	// On Linux, FMOD causes a SIGPIPE for some netstream error
	// conditions (an FMOD bug); ignore SIGPIPE so it doesn't crash us.
	// NOW FIXED in FMOD 3.x since 2006-10-01.
	//signal(SIGPIPE, SIG_IGN);

	// We're interested in logging which output method we
	// ended up with, for QA purposes.
	switch (FSOUND_GetOutput())
	{
	case FSOUND_OUTPUT_NOSOUND: LL_DEBUGS("AppInit") << "Audio output: NoSound" << LL_ENDL; break;
	case FSOUND_OUTPUT_OSS:	LL_DEBUGS("AppInit") << "Audio output: OSS" << LL_ENDL; break;
	case FSOUND_OUTPUT_ESD:	LL_DEBUGS("AppInit") << "Audio output: ESD" << LL_ENDL; break;
	case FSOUND_OUTPUT_ALSA: LL_DEBUGS("AppInit") << "Audio output: ALSA" << LL_ENDL; break;
	default: LL_INFOS("AppInit") << "Audio output: Unknown!" << LL_ENDL; break;
	};

#else // LL_LINUX

	// initialize the FMOD engine
	if (!FSOUND_Init(44100, num_channels, fmod_flags))
	{
		LL_WARNS("AppInit") << "Error initializing FMOD: "
			<< FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
		return FALSE;
	}
	
#endif

	initInternetStream();

	LL_DEBUGS("AppInit") << "LLAudioEngine_FMOD::init() FMOD initialized correctly" << LL_ENDL;

	mInited = TRUE;

	return TRUE;
}


void LLAudioEngine_FMOD::idle(F32 max_decode_time)
{
	LLAudioEngine::idle(max_decode_time);

	updateInternetStream();
}


void LLAudioEngine_FMOD::allocateListener(void)
{	
	mListenerp = (LLListener *) new LLListener_FMOD();
	if (!mListenerp)
	{
		llwarns << "Listener creation failed" << llendl;
	}
}


void LLAudioEngine_FMOD::shutdown()
{
	if (gWindDSP)
	{
		FSOUND_DSP_SetActive(gWindDSP,FALSE);
		FSOUND_DSP_Free(gWindDSP);
	}

	stopInternetStream();

	LLAudioEngine::shutdown();
	
	llinfos << "LLAudioEngine_FMOD::shutdown() closing FMOD" << llendl;
	FSOUND_Close();
	llinfos << "LLAudioEngine_FMOD::shutdown() done closing FMOD" << llendl;

	delete mListenerp;
	mListenerp = NULL;
}


LLAudioBuffer *LLAudioEngine_FMOD::createBuffer()
{
	return new LLAudioBufferFMOD();
}


LLAudioChannel *LLAudioEngine_FMOD::createChannel()
{
	return new LLAudioChannelFMOD();
}


void LLAudioEngine_FMOD::initWind()
{
	if (!gWindDSP)
	{
		gWindDSP = FSOUND_DSP_Create(&windCallback, FSOUND_DSP_DEFAULTPRIORITY_CLEARUNIT + 20, NULL);
	}
	if (gWindDSP)
	{
		FSOUND_DSP_SetActive(gWindDSP, TRUE);
	}
	mNextWindUpdate = 0.0;
}


void LLAudioEngine_FMOD::cleanupWind()
{
	if (gWindDSP)
	{
		FSOUND_DSP_SetActive(gWindDSP, FALSE);
		FSOUND_DSP_Free(gWindDSP);
		gWindDSP = NULL;
	}
}


//-----------------------------------------------------------------------
void LLAudioEngine_FMOD::updateWind(LLVector3 wind_vec, F32 camera_height_above_water)
{
	LLVector3 wind_pos;
	F64 pitch;
	F64 center_freq;

	if (!mEnableWind)
	{
		return;
	}
	
	if (mWindUpdateTimer.checkExpirationAndReset(LL_WIND_UPDATE_INTERVAL))
	{
		
		// wind comes in as Linden coordinate (+X = forward, +Y = left, +Z = up)
		// need to convert this to the conventional orientation DS3D and OpenAL use
		// where +X = right, +Y = up, +Z = backwards

		wind_vec.setVec(-wind_vec.mV[1], wind_vec.mV[2], -wind_vec.mV[0]);

		// cerr << "Wind update" << endl;

		pitch = 1.0 + mapWindVecToPitch(wind_vec);
		center_freq = 80.0 * pow(pitch,2.5*(mapWindVecToGain(wind_vec)+1.0));
		
		gTargetFreq = (F32)center_freq;
		gTargetGain = (F32)mapWindVecToGain(wind_vec) * mMaxWindGain;
		gTargetPanGainR = (F32)mapWindVecToPan(wind_vec);
  	}
}

/*
//-----------------------------------------------------------------------
void LLAudioEngine_FMOD::setSourceMinDistance(U16 source_num, F64 distance)
{
	if (!mInited)
	{
		return;
	}
	if (mBuffer[source_num])
	{
		mMinDistance[source_num] = (F32) distance;
		if (!FSOUND_Sample_SetMinMaxDistance(mBuffer[source_num],mMinDistance[source_num], mMaxDistance[source_num]))
		{
			llwarns << "FMOD::setSourceMinDistance(" << source_num << "), error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
		}
	}
}

//-----------------------------------------------------------------------
void LLAudioEngine_FMOD::setSourceMaxDistance(U16 source_num, F64 distance)
{
	if (!mInited)
	{
		return;
	}
	if (mBuffer[source_num])
	{
		mMaxDistance[source_num] = (F32) distance;
		if (!FSOUND_Sample_SetMinMaxDistance(mBuffer[source_num],mMinDistance[source_num], mMaxDistance[source_num]))
		{
			llwarns << "FMOD::setSourceMaxDistance(" << source_num << "), error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
		}
	}
}

//-----------------------------------------------------------------------
void LLAudioEngine_FMOD::get3DParams(S32 source_num, S32 *volume, S32 *freq, S32 *inside, S32 *outside, LLVector3 *orient, S32 *out_volume, F32 *min_dist, F32 *max_dist)
{
	*volume = 0;
	*freq = 0;
	*inside = 0;
	*outside = 0;
	*orient = LLVector3::zero;
	*out_volume = 0;
	*min_dist = 0.f;
	*max_dist = 0.f;
}

*/


//-----------------------------------------------------------------------
void LLAudioEngine_FMOD::setInternalGain(F32 gain)
{
	if (!mInited)
	{
		return;
	}

	gain = llclamp( gain, 0.0f, 1.0f );
	FSOUND_SetSFXMasterVolume( llround( 255.0f * gain ) );

	if ( mInternetStreamChannel != -1 )
	{
		F32 clamp_internet_stream_gain = llclamp( mInternetStreamGain, 0.0f, 1.0f );
		FSOUND_SetVolumeAbsolute( mInternetStreamChannel, llround( 255.0f * clamp_internet_stream_gain ) );
	}
}

//
// LLAudioChannelFMOD implementation
//

LLAudioChannelFMOD::LLAudioChannelFMOD() : LLAudioChannel(), mChannelID(0), mLastSamplePos(0)
{
}


LLAudioChannelFMOD::~LLAudioChannelFMOD()
{
	cleanup();
}


BOOL LLAudioChannelFMOD::updateBuffer()
{
	if (LLAudioChannel::updateBuffer())
	{
		// Base class update returned TRUE, which means that we need to actually
		// set up the channel for a different buffer.

		LLAudioBufferFMOD *bufferp = (LLAudioBufferFMOD *)mCurrentSourcep->getCurrentBuffer();

		// Grab the FMOD sample associated with the buffer
		FSOUND_SAMPLE *samplep = bufferp->getSample();
		if (!samplep)
		{
			// This is bad, there should ALWAYS be a sample associated with a legit
			// buffer.
			llerrs << "No FMOD sample!" << llendl;
			return FALSE;
		}


		// Actually play the sound.  Start it off paused so we can do all the necessary
		// setup.
		mChannelID = FSOUND_PlaySoundEx(FSOUND_FREE, samplep, FSOUND_DSP_GetSFXUnit(), TRUE);

		//llinfos << "Setting up channel " << std::hex << mChannelID << std::dec << llendl;
	}

	// If we have a source for the channel, we need to update its gain.
	if (mCurrentSourcep)
	{
		// SJB: warnings can spam and hurt framerate, disabling
		if (!FSOUND_SetVolume(mChannelID, llround(mCurrentSourcep->getGain() * 255.0f)))
		{
// 			llwarns << "LLAudioChannelFMOD::updateBuffer error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
		}
		
		if (!FSOUND_SetLoopMode(mChannelID, mCurrentSourcep->isLoop() ? FSOUND_LOOP_NORMAL : FSOUND_LOOP_OFF))
		{
// 			llwarns << "Channel " << mChannelID << "Source ID: " << mCurrentSourcep->getID()
// 					<< " at " << mCurrentSourcep->getPositionGlobal() << llendl;
// 			llwarns << "LLAudioChannelFMOD::updateBuffer error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
		}
	}

	return TRUE;
}


void LLAudioChannelFMOD::update3DPosition()
{
	if (!mChannelID)
	{
		// We're not actually a live channel (i.e., we're not playing back anything)
		return;
	}

	LLAudioBufferFMOD *bufferp = (LLAudioBufferFMOD *)mCurrentBufferp;
	if (!bufferp)
	{
		// We don't have a buffer associated with us (should really have been picked up
		// by the above if.
		return;
	}

	if (mCurrentSourcep->isAmbient())
	{
		// Ambient sound, don't need to do any positional updates.
		bufferp->set3DMode(FALSE);
	}
	else
	{
		// Localized sound.  Update the position and velocity of the sound.
		bufferp->set3DMode(TRUE);

		LLVector3 float_pos;
		float_pos.setVec(mCurrentSourcep->getPositionGlobal());
		if (!FSOUND_3D_SetAttributes(mChannelID, float_pos.mV, mCurrentSourcep->getVelocity().mV))
		{
			LL_DEBUGS("FMOD") << "LLAudioChannelFMOD::update3DPosition error: " << FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
		}
	}
}


void LLAudioChannelFMOD::updateLoop()
{
	if (!mChannelID)
	{
		// May want to clear up the loop/sample counters.
		return;
	}

	//
	// Hack:  We keep track of whether we looped or not by seeing when the sign of the last sample
	// flips.  This is pretty crappy.
	//
	U32 cur_pos = FSOUND_GetCurrentPosition(mChannelID);
	if (cur_pos < (U32)mLastSamplePos)
	{
		mLoopedThisFrame = TRUE;
	}
	mLastSamplePos = cur_pos;
}


void LLAudioChannelFMOD::cleanup()
{
	if (!mChannelID)
	{
		//llinfos << "Aborting cleanup with no channelID." << llendl;
		return;
	}

	//llinfos << "Cleaning up channel: " << mChannelID << llendl;
	if (!FSOUND_StopSound(mChannelID))
	{
		LL_DEBUGS("FMOD") << "LLAudioChannelFMOD::cleanup error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
	}

	mCurrentBufferp = NULL;
	mChannelID = 0;
}


void LLAudioChannelFMOD::play()
{
	if (!mChannelID)
	{
		llwarns << "Playing without a channelID, aborting" << llendl;
		return;
	}

	if (!FSOUND_SetPaused(mChannelID, FALSE))
	{
		llwarns << "LLAudioChannelFMOD::play error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
	}
	getSource()->setPlayedOnce(TRUE);
}


void LLAudioChannelFMOD::playSynced(LLAudioChannel *channelp)
{
	LLAudioChannelFMOD *fmod_channelp = (LLAudioChannelFMOD*)channelp;
	if (!(fmod_channelp->mChannelID && mChannelID))
	{
		// Don't have channels allocated to both the master and the slave
		return;
	}

	U32 position = FSOUND_GetCurrentPosition(fmod_channelp->mChannelID) % mCurrentBufferp->getLength();
	// Try to match the position of our sync master
	if (!FSOUND_SetCurrentPosition(mChannelID, position))
	{
		llwarns << "LLAudioChannelFMOD::playSynced unable to set current position" << llendl;
	}

	// Start us playing
	play();
}


BOOL LLAudioChannelFMOD::isPlaying()
{
	if (!mChannelID)
	{
		return FALSE;
	}

	return FSOUND_IsPlaying(mChannelID) && (!FSOUND_GetPaused(mChannelID));
}



//
// LLAudioBufferFMOD implementation
//


LLAudioBufferFMOD::LLAudioBufferFMOD()
{
	mSamplep = NULL;
}


LLAudioBufferFMOD::~LLAudioBufferFMOD()
{
	if (mSamplep)
	{
		// Clean up the associated FMOD sample if it exists.
		FSOUND_Sample_Free(mSamplep);
		mSamplep = NULL;
	}
}


BOOL LLAudioBufferFMOD::loadWAV(const std::string& filename)
{
	// Try to open a wav file from disk.  This will eventually go away, as we don't
	// really want to block doing this.
	if (filename.empty())
	{
		// invalid filename, abort.
		return FALSE;
	}

	S32 file_size = 0;
	apr_file_t* apr_file = ll_apr_file_open(filename, LL_APR_RPB, &file_size);
	if (!apr_file)
	{
		// File not found, abort.
		return FALSE;
	}
	apr_file_close(apr_file);

	if (mSamplep)
	{
		// If there's already something loaded in this buffer, clean it up.
		FSOUND_Sample_Free(mSamplep);
		mSamplep = NULL;
	}

	// Load up the wav file into an fmod sample
#if LL_WINDOWS
	// MikeS. - Loading the sound file manually and then handing it over to FMOD,
	//	since FMOD uses posix IO internally,
	// which doesn't work with unicode file paths.
	LLFILE* sound_file = LLFile::fopen(filename,"rb");	/* Flawfinder: ignore */
	if (sound_file)
	{
		fseek(sound_file,0,SEEK_END);
		U32	file_length = ftell(sound_file);	//Find the length of the file by seeking to the end and getting the offset
		size_t	read_count;
		fseek(sound_file,0,SEEK_SET);	//Seek back to the beginning
		char*	buffer = new char[file_length];
		llassert(buffer);
		read_count = fread((void*)buffer,file_length,1,sound_file);//Load it..
		if(ferror(sound_file)==0 && (read_count == 1)){//No read error, and we got 1 chunk of our size...
			unsigned int mode_flags = FSOUND_LOOP_NORMAL | FSOUND_LOADMEMORY;
									//FSOUND_16BITS | FSOUND_MONO | FSOUND_LOADMEMORY | FSOUND_LOOP_NORMAL;
			mSamplep = FSOUND_Sample_Load(FSOUND_UNMANAGED, buffer, mode_flags , 0, file_length);
		}
		delete[] buffer;
		fclose(sound_file);
	}
#else
	mSamplep = FSOUND_Sample_Load(FSOUND_UNMANAGED, filename.c_str(), FSOUND_LOOP_NORMAL, 0, 0);
#endif

	if (!mSamplep)
	{
		// We failed to load the file for some reason.
		llwarns << "Could not load data '" << filename << "': "
				<< FMOD_ErrorString(FSOUND_GetError()) << llendl;

		//
		// If we EVER want to load wav files provided by end users, we need
		// to rethink this!
		//
		// file is probably corrupt - remove it.
		LLFile::remove(filename);
		return FALSE;
	}

	// Everything went well, return TRUE
	return TRUE;
}


U32 LLAudioBufferFMOD::getLength()
{
	if (!mSamplep)
	{
		return 0;
	}

	return FSOUND_Sample_GetLength(mSamplep);
}


void LLAudioBufferFMOD::set3DMode(BOOL use3d)
{
	U16 current_mode = FSOUND_Sample_GetMode(mSamplep);
	
	if (use3d)
	{
		if (!FSOUND_Sample_SetMode(mSamplep, (current_mode & (~FSOUND_2D))))
		{
			llwarns << "LLAudioBufferFMOD::set3DMode error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
		}
	}
	else
	{
		if (!FSOUND_Sample_SetMode(mSamplep, current_mode | FSOUND_2D))
		{
			llwarns << "LLAudioBufferFMOD::set3DMode error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
		}
	}
}



//---------------------------------------------------------------------------
// Internet Streaming
//---------------------------------------------------------------------------
void LLAudioEngine_FMOD::initInternetStream()
{
	// Number of milliseconds of audio to buffer for the audio card.
	// Must be larger than the usual Second Life frame stutter time.
    FSOUND_Stream_SetBufferSize(200);

	// Here's where we set the size of the network buffer and some buffering 
	// parameters.  In this case we want a network buffer of 16k, we want it 
	// to prebuffer 40% of that when we first connect, and we want it 
	// to rebuffer 80% of that whenever we encounter a buffer underrun.

	// Leave the net buffer properties at the default.
	//FSOUND_Stream_Net_SetBufferProperties(20000, 40, 80);
	mInternetStreamURL.clear();
}


void LLAudioEngine_FMOD::startInternetStream(const std::string& url)
{
	if (!mInited)
	{
		llwarns << "startInternetStream before audio initialized" << llendl;
		return;
	}

	// "stop" stream but don't clear url, etc. in calse url == mInternetStreamURL
	stopInternetStream();
	if (!url.empty())
	{
		llinfos << "Starting internet stream: " << url << llendl;
		mCurrentInternetStreamp = new LLAudioStreamFMOD(url);
		mInternetStreamURL = url;
	}
	else
	{
		llinfos << "Set internet stream to null" << llendl;
		mInternetStreamURL.clear();
	}
}


signed char F_CALLBACKAPI LLAudioEngine_FMOD::callbackMetaData(char *name, char *value, void *userdata)
{
	/*
	LLAudioEngine_FMOD* self = (LLAudioEngine_FMOD*)userdata;

    if (!strcmp("ARTIST", name))
    {
        strlcpy(self->mInternetStreamArtist, value, 256);
        self->mInternetStreamNewMetaData = TRUE;
        return TRUE;
    }

    if (!strcmp("TITLE", name))
    {
        strlcpy(self->mInternetStreamTitle, value, 256);
        self->mInternetStreamNewMetaData = TRUE;
        return TRUE;
    }
	*/

    return TRUE;
}


void LLAudioEngine_FMOD::updateInternetStream()
{
	// Kill dead internet streams, if possible
	std::list<LLAudioStreamFMOD *>::iterator iter;
	for (iter = mDeadStreams.begin(); iter != mDeadStreams.end();)
	{
		LLAudioStreamFMOD *streamp = *iter;
		if (streamp->stopStream())
		{
			llinfos << "Closed dead stream" << llendl;
			delete streamp;
			mDeadStreams.erase(iter++);
		}
		else
		{
			iter++;
		}
	}

	// Don't do anything if there are no streams playing
	if (!mCurrentInternetStreamp)
	{
		return;
	}

    int open_state = mCurrentInternetStreamp->getOpenState();

	if (!open_state)
	{
		// Stream is live


		// start the stream if it's ready
		if (mInternetStreamChannel < 0)
		{
			mInternetStreamChannel = mCurrentInternetStreamp->startStream();

			if (mInternetStreamChannel != -1)
			{
				// Reset volume to previously set volume
				setInternetStreamGain(mInternetStreamGain);
				FSOUND_SetPaused(mInternetStreamChannel, FALSE);
				//FSOUND_Stream_Net_SetMetadataCallback(mInternetStream, callbackMetaData, this);
			}
		}
	}
		
	switch(open_state)
	{
	default:
	case 0:
		// success
		break;
	case -1:
		// stream handle is invalid
		llwarns << "InternetStream - invalid handle" << llendl;
		stopInternetStream();
		return;
	case -2:
		// opening
		//strlcpy(mInternetStreamArtist, "Opening", 256);
		break;
	case -3:
		// failed to open, file not found, perhaps
		llwarns << "InternetSteam - failed to open" << llendl;
		stopInternetStream();
		return;
	case -4:
		// connecting
		//strlcpy(mInternetStreamArtist, "Connecting", 256);
		break;
	case -5:
		// buffering
		//strlcpy(mInternetStreamArtist, "Buffering", 256);
		break;
	}

}

void LLAudioEngine_FMOD::stopInternetStream()
{
	if (mInternetStreamChannel != -1)
	{
		FSOUND_SetPaused(mInternetStreamChannel, TRUE);
		FSOUND_SetPriority(mInternetStreamChannel, 0);
		mInternetStreamChannel = -1;
	}

	if (mCurrentInternetStreamp)
	{
		llinfos << "Stopping internet stream: " << mCurrentInternetStreamp->getURL() << llendl;
		if (mCurrentInternetStreamp->stopStream())
		{
			delete mCurrentInternetStreamp;
		}
		else
		{
			llwarns << "Pushing stream to dead list: " << mCurrentInternetStreamp->getURL() << llendl;
			mDeadStreams.push_back(mCurrentInternetStreamp);
		}
		mCurrentInternetStreamp = NULL;
		//mInternetStreamURL.clear();
	}
}

void LLAudioEngine_FMOD::pauseInternetStream(int pause)
{
	if (pause < 0)
	{
		pause = mCurrentInternetStreamp ? 1 : 0;
	}

	if (pause)
	{
		if (mCurrentInternetStreamp)
		{
			stopInternetStream();
		}
	}
	else
	{
		startInternetStream(mInternetStreamURL);
	}
}


// A stream is "playing" if it has been requested to start.  That
// doesn't necessarily mean audio is coming out of the speakers.
int LLAudioEngine_FMOD::isInternetStreamPlaying()
{
	if (mCurrentInternetStreamp)
	{
		return 1; // Active and playing
	}
	else if (!mInternetStreamURL.empty())
	{
		return 2; // "Paused"
	}
	else
	{
		return 0;
	}
}


void LLAudioEngine_FMOD::getInternetStreamInfo(char* artist_out, char* title_out)
{
	//strlcpy(artist_out, mInternetStreamArtist, 256);
	//strlcpy(title_out, mInternetStreamTitle, 256);
}


void LLAudioEngine_FMOD::setInternetStreamGain(F32 vol)
{
	LLAudioEngine::setInternetStreamGain(vol);
	if (mInternetStreamChannel != -1)
	{
		vol = llclamp(vol, 0.f, 1.f);
		int vol_int = llround(vol * 255.f);
		FSOUND_SetVolumeAbsolute(mInternetStreamChannel, vol_int);
	}
}


const std::string& LLAudioEngine_FMOD::getInternetStreamURL()
{
	return mInternetStreamURL;
}


LLAudioStreamFMOD::LLAudioStreamFMOD(const std::string& url) :
	mInternetStream(NULL),
	mReady(FALSE)
{
	mInternetStreamURL = url;
	mInternetStream = FSOUND_Stream_Open(url.c_str(), FSOUND_NORMAL | FSOUND_NONBLOCKING, 0, 0);
	if (!mInternetStream)
	{
		llwarns << "Couldn't open fmod stream, error "
			<< FMOD_ErrorString(FSOUND_GetError())
			<< llendl;
		mReady = FALSE;
		return;
	}

	mReady = TRUE;
}

int LLAudioStreamFMOD::startStream()
{
	// We need a live and opened stream before we try and play it.
	if (!mInternetStream || getOpenState())
	{
		llwarns << "No internet stream to start playing!" << llendl;
		return -1;
	}

	// Make sure the stream is set to 2D mode.
	FSOUND_Stream_SetMode(mInternetStream, FSOUND_2D);

	return FSOUND_Stream_PlayEx(FSOUND_FREE, mInternetStream, NULL, TRUE);
}

BOOL LLAudioStreamFMOD::stopStream()
{
	if (mInternetStream)
	{
		int read_percent = 0;
		int status = 0;
		int bitrate = 0;
		unsigned int flags = 0x0;
		FSOUND_Stream_Net_GetStatus(mInternetStream, &status, &read_percent, &bitrate, &flags);

		BOOL close = TRUE;
		switch (status)
		{
		case FSOUND_STREAM_NET_CONNECTING:
			close = FALSE;
			break;
		case FSOUND_STREAM_NET_NOTCONNECTED:
		case FSOUND_STREAM_NET_BUFFERING:
		case FSOUND_STREAM_NET_READY:
		case FSOUND_STREAM_NET_ERROR:
		default:
			close = TRUE;
		}

		if (close)
		{
			FSOUND_Stream_Close(mInternetStream);
			mInternetStream = NULL;
			return TRUE;
		}
		else
		{
			return FALSE;
		}
	}
	else
	{
		return TRUE;
	}
}

int LLAudioStreamFMOD::getOpenState()
{
	int open_state = FSOUND_Stream_GetOpenState(mInternetStream);
	return open_state;
}

/* This determines the format of the mixbuffer being passed in. change if you want to support int32 or float32 */
#if LL_DARWIN
	#define MIXBUFFERFORMAT S32
#else
	#define MIXBUFFERFORMAT S16
#endif

inline MIXBUFFERFORMAT clipSample(MIXBUFFERFORMAT sample, MIXBUFFERFORMAT min, MIXBUFFERFORMAT max)
{
	if (sample > max)
		sample = max;
	else if (sample < min)
		sample = min;
	
	return sample;
}

void * F_CALLBACKAPI windCallback(void *originalbuffer, void *newbuffer, int length, void*)
{
// originalbuffer = fsounds original mixbuffer.
// newbuffer = the buffer passed from the previous DSP unit.
// length = length in samples at this mix time.
// param = user parameter passed through in FSOUND_DSP_Create.
//
// modify the buffer in some fashion

	U8 *cursamplep = (U8*)newbuffer;
	U8   wordsize = 2;

#if LL_DARWIN
	wordsize = sizeof(MIXBUFFERFORMAT);
#else
    int		mixertype = FSOUND_GetMixer();
    if (mixertype == FSOUND_MIXER_BLENDMODE || mixertype == FSOUND_MIXER_QUALITY_FPU)
    {
		wordsize = 4;
    }
#endif

    double bandwidth = 50;
    double inputSamplingRate = 44100;
	double a0,b1,b2;
 
	// calculate resonant filter coeffs
    b2 = exp(-(F_TWO_PI) * (bandwidth / inputSamplingRate));

	while (length--)
	{
		gCurrentFreq = (float)((0.999 * gCurrentFreq) + (0.001 * gTargetFreq));
		gCurrentGain = (float)((0.999 * gCurrentGain) + (0.001 * gTargetGain));
		gCurrentPanGainR = (float)((0.999 * gCurrentPanGainR) + (0.001 * gTargetPanGainR));
		b1 = (-4.0 * b2) / (1.0 + b2) * cos(F_TWO_PI * (gCurrentFreq / inputSamplingRate));
	    a0 = (1.0 - b2) * sqrt(1.0 - (b1 * b1) / (4.0 * b2));
		double nextSample;

	    // start with white noise
		nextSample = ll_frand(2.0f) - 1.0f;
									 
#if 1 // LLAE_WIND_PINK apply pinking filter
		gbuf0 = 0.997f * gbuf0 + 0.0126502f * nextSample; 
        gbuf1 = 0.985f * gbuf1 + 0.0139083f * nextSample;
        gbuf2 = 0.950f * gbuf2 + 0.0205439f * nextSample;
        gbuf3 = 0.850f * gbuf3 + 0.0387225f * nextSample;
        gbuf4 = 0.620f * gbuf4 + 0.0465932f * nextSample;
        gbuf5 = 0.250f * gbuf5 + 0.1093477f * nextSample;
			  
        nextSample = gbuf0 + gbuf1 + gbuf2 + gbuf3 + gbuf4 + gbuf5;
#endif
			
#if 1 //LLAE_WIND_RESONANT // do a resonant filter on the noise
        nextSample = (double)( a0 * nextSample - b1 * gY0 - b2 * gY1 );

        gY1 = gY0;
        gY0 = nextSample;
#endif

	    nextSample *= gCurrentGain;
		
		MIXBUFFERFORMAT	sample;

		sample = llfloor(((F32)nextSample*32768.f*(1.0f - gCurrentPanGainR))+0.5f);
		*(MIXBUFFERFORMAT*)cursamplep = clipSample((*(MIXBUFFERFORMAT*)cursamplep) + sample, -32768, 32767);
		cursamplep += wordsize;

		sample = llfloor(((F32)nextSample*32768.f*gCurrentPanGainR)+0.5f);
		*(MIXBUFFERFORMAT*)cursamplep = clipSample((*(MIXBUFFERFORMAT*)cursamplep) + sample, -32768, 32767);
		cursamplep += wordsize;
	}

	return newbuffer;
}
