Apertura del repositorio

This commit is contained in:
2025-05-24 18:09:39 -03:00
parent 76e6359dad
commit d883ddd0d0
35253 changed files with 2891973 additions and 2 deletions
+72
View File
@@ -0,0 +1,72 @@
# -*- cmake -*-
project(llaudio)
include(00-Common)
include(LLAudio)
include(FMODSTUDIO)
include(OPENAL)
include(LLCommon)
include(LLWindow)
set(llaudio_SOURCE_FILES
llaudioengine.cpp
lllistener.cpp
llaudiodecodemgr.cpp
llvorbisencode.cpp
)
set(llaudio_HEADER_FILES
CMakeLists.txt
llaudioengine.h
lllistener.h
llaudiodecodemgr.h
llvorbisencode.h
llwindgen.h
)
if (TARGET ll::fmodstudio)
list(APPEND llaudio_SOURCE_FILES
llaudioengine_fmodstudio.cpp
lllistener_fmodstudio.cpp
llstreamingaudio_fmodstudio.cpp
)
list(APPEND llaudio_HEADER_FILES
llaudioengine_fmodstudio.h
lllistener_fmodstudio.h
llstreamingaudio_fmodstudio.h
)
endif ()
if (TARGET ll::openal)
list(APPEND llaudio_SOURCE_FILES
llaudioengine_openal.cpp
lllistener_openal.cpp
)
list(APPEND llaudio_HEADER_FILES
llaudioengine_openal.h
lllistener_openal.h
)
endif ()
list(APPEND llaudio_SOURCE_FILES ${llaudio_HEADER_FILES})
add_library (llaudio ${llaudio_SOURCE_FILES})
target_include_directories( llaudio INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries( llaudio
llcommon
llmath
llmessage
llfilesystem
ll::vorbis
)
if( TARGET ll::openal )
target_link_libraries( llaudio ll::openal )
endif()
if( TARGET ll::fmodstudio )
target_link_libraries( llaudio ll::fmodstudio )
endif()
+820
View File
@@ -0,0 +1,820 @@
/**
* @file llaudiodecodemgr.cpp
*
* $LicenseInfo:firstyear=2003&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 "linden_common.h"
#include "llaudiodecodemgr.h"
#include "llaudioengine.h"
#include "lllfsthread.h"
#include "llfilesystem.h"
#include "llstring.h"
#include "lldir.h"
#include "llendianswizzle.h"
#include "llassetstorage.h"
#include "llrefcount.h"
#include "threadpool.h"
#include "workqueue.h"
#include "llvorbisencode.h"
#include "vorbis/codec.h"
#include "vorbis/vorbisfile.h"
#include <iterator>
#include <deque>
extern LLAudioEngine *gAudiop;
static const S32 WAV_HEADER_SIZE = 44;
//////////////////////////////////////////////////////////////////////////////
class LLVorbisDecodeState : public LLThreadSafeRefCount
{
public:
class WriteResponder : public LLLFSThread::Responder
{
public:
WriteResponder(LLVorbisDecodeState* decoder) : mDecoder(decoder) {}
~WriteResponder() {}
void completed(S32 bytes)
{
mDecoder->ioComplete(bytes);
}
LLPointer<LLVorbisDecodeState> mDecoder;
};
LLVorbisDecodeState(const LLUUID &uuid, const std::string &out_filename);
bool initDecode();
bool decodeSection(); // Return true if done.
bool finishDecode();
void flushBadFile();
void ioComplete(S32 bytes) { mBytesRead = bytes; }
bool isValid() const { return mValid; }
bool isDone() const { return mDone; }
const LLUUID &getUUID() const { return mUUID; }
protected:
virtual ~LLVorbisDecodeState();
bool mValid;
bool mDone;
LLAtomicS32 mBytesRead;
LLUUID mUUID;
std::vector<U8> mWAVBuffer;
std::string mOutFilename;
LLLFSThread::handle_t mFileHandle;
LLFileSystem *mInFilep;
OggVorbis_File mVF;
S32 mCurrentSection;
};
size_t cache_read(void *ptr, size_t size, size_t nmemb, void *datasource)
{
LLFileSystem *file = (LLFileSystem *)datasource;
if (file->read((U8*)ptr, (S32)(size * nmemb))) /*Flawfinder: ignore*/
{
S32 read = file->getLastBytesRead();
return read / size; /*Flawfinder: ignore*/
}
else
{
return 0;
}
}
S32 cache_seek(void *datasource, ogg_int64_t offset, S32 whence)
{
LLFileSystem *file = (LLFileSystem *)datasource;
// cache has 31-bit files
if (offset > S32_MAX)
{
return -1;
}
S32 origin;
switch (whence) {
case SEEK_SET:
origin = 0;
break;
case SEEK_END:
origin = file->getSize();
break;
case SEEK_CUR:
origin = -1;
break;
default:
LL_ERRS("AudioEngine") << "Invalid whence argument to cache_seek" << LL_ENDL;
return -1;
}
if (file->seek((S32)offset, origin))
{
return 0;
}
else
{
return -1;
}
}
S32 cache_close (void *datasource)
{
LLFileSystem *file = (LLFileSystem *)datasource;
delete file;
return 0;
}
long cache_tell (void *datasource)
{
LLFileSystem *file = (LLFileSystem *)datasource;
return file->tell();
}
LLVorbisDecodeState::LLVorbisDecodeState(const LLUUID &uuid, const std::string &out_filename)
{
mDone = false;
mValid = false;
mBytesRead = -1;
mUUID = uuid;
mInFilep = NULL;
mCurrentSection = 0;
mOutFilename = out_filename;
mFileHandle = LLLFSThread::nullHandle();
// No default value for mVF, it's an ogg structure?
// Hey, let's zero it anyway, for predictability.
memset(&mVF, 0, sizeof(mVF));
}
LLVorbisDecodeState::~LLVorbisDecodeState()
{
if (!mDone)
{
delete mInFilep;
mInFilep = NULL;
}
}
bool LLVorbisDecodeState::initDecode()
{
ov_callbacks cache_callbacks;
cache_callbacks.read_func = cache_read;
cache_callbacks.seek_func = cache_seek;
cache_callbacks.close_func = cache_close;
cache_callbacks.tell_func = cache_tell;
LL_DEBUGS("AudioEngine") << "Initing decode from vfile: " << mUUID << LL_ENDL;
mInFilep = new LLFileSystem(mUUID, LLAssetType::AT_SOUND);
if (!mInFilep || !mInFilep->getSize())
{
LL_WARNS("AudioEngine") << "unable to open vorbis source vfile for reading" << LL_ENDL;
delete mInFilep;
mInFilep = NULL;
return false;
}
S32 r = ov_open_callbacks(mInFilep, &mVF, NULL, 0, cache_callbacks);
if(r < 0)
{
LL_WARNS("AudioEngine") << r << " Input to vorbis decode does not appear to be an Ogg bitstream: " << mUUID << LL_ENDL;
return(false);
}
S32 sample_count = (S32)ov_pcm_total(&mVF, -1);
size_t size_guess = (size_t)sample_count;
vorbis_info* vi = ov_info(&mVF, -1);
size_guess *= (vi? vi->channels : 1);
size_guess *= 2;
size_guess += 2048;
bool abort_decode = false;
if (vi)
{
if( vi->channels < 1 || vi->channels > LLVORBIS_CLIP_MAX_CHANNELS )
{
abort_decode = true;
LL_WARNS("AudioEngine") << "Bad channel count: " << vi->channels << LL_ENDL;
}
}
else // !vi
{
abort_decode = true;
LL_WARNS("AudioEngine") << "No default bitstream found" << LL_ENDL;
}
if( (size_t)sample_count > LLVORBIS_CLIP_REJECT_SAMPLES ||
(size_t)sample_count <= 0)
{
abort_decode = true;
LL_WARNS("AudioEngine") << "Illegal sample count: " << sample_count << LL_ENDL;
}
if( size_guess > LLVORBIS_CLIP_REJECT_SIZE )
{
abort_decode = true;
LL_WARNS("AudioEngine") << "Illegal sample size: " << size_guess << LL_ENDL;
}
if( abort_decode )
{
LL_WARNS("AudioEngine") << "Canceling initDecode. Bad asset: " << mUUID << LL_ENDL;
vorbis_comment* comment = ov_comment(&mVF,-1);
if (comment && comment->vendor)
{
LL_WARNS("AudioEngine") << "Bad asset encoded by: " << comment->vendor << LL_ENDL;
}
delete mInFilep;
mInFilep = NULL;
return false;
}
try
{
mWAVBuffer.reserve(size_guess);
mWAVBuffer.resize(WAV_HEADER_SIZE);
}
catch (std::bad_alloc&)
{
LL_WARNS("AudioEngine") << "Out of memory when trying to alloc buffer: " << size_guess << LL_ENDL;
delete mInFilep;
mInFilep = NULL;
return false;
}
{
// write the .wav format header
//"RIFF"
mWAVBuffer[0] = 0x52;
mWAVBuffer[1] = 0x49;
mWAVBuffer[2] = 0x46;
mWAVBuffer[3] = 0x46;
// length = datalen + 36 (to be filled in later)
mWAVBuffer[4] = 0x00;
mWAVBuffer[5] = 0x00;
mWAVBuffer[6] = 0x00;
mWAVBuffer[7] = 0x00;
//"WAVE"
mWAVBuffer[8] = 0x57;
mWAVBuffer[9] = 0x41;
mWAVBuffer[10] = 0x56;
mWAVBuffer[11] = 0x45;
// "fmt "
mWAVBuffer[12] = 0x66;
mWAVBuffer[13] = 0x6D;
mWAVBuffer[14] = 0x74;
mWAVBuffer[15] = 0x20;
// chunk size = 16
mWAVBuffer[16] = 0x10;
mWAVBuffer[17] = 0x00;
mWAVBuffer[18] = 0x00;
mWAVBuffer[19] = 0x00;
// format (1 = PCM)
mWAVBuffer[20] = 0x01;
mWAVBuffer[21] = 0x00;
// number of channels
mWAVBuffer[22] = 0x01;
mWAVBuffer[23] = 0x00;
// samples per second
mWAVBuffer[24] = 0x44;
mWAVBuffer[25] = 0xAC;
mWAVBuffer[26] = 0x00;
mWAVBuffer[27] = 0x00;
// average bytes per second
mWAVBuffer[28] = 0x88;
mWAVBuffer[29] = 0x58;
mWAVBuffer[30] = 0x01;
mWAVBuffer[31] = 0x00;
// bytes to output at a single time
mWAVBuffer[32] = 0x02;
mWAVBuffer[33] = 0x00;
// 16 bits per sample
mWAVBuffer[34] = 0x10;
mWAVBuffer[35] = 0x00;
// "data"
mWAVBuffer[36] = 0x64;
mWAVBuffer[37] = 0x61;
mWAVBuffer[38] = 0x74;
mWAVBuffer[39] = 0x61;
// these are the length of the data chunk, to be filled in later
mWAVBuffer[40] = 0x00;
mWAVBuffer[41] = 0x00;
mWAVBuffer[42] = 0x00;
mWAVBuffer[43] = 0x00;
}
//{
//char **ptr=ov_comment(&mVF,-1)->user_comments;
// vorbis_info *vi=ov_info(&vf,-1);
//while(*ptr){
// fprintf(stderr,"%s\n",*ptr);
// ++ptr;
//}
// fprintf(stderr,"\nBitstream is %d channel, %ldHz\n",vi->channels,vi->rate);
// fprintf(stderr,"\nDecoded length: %ld samples\n", (long)ov_pcm_total(&vf,-1));
// fprintf(stderr,"Encoded by: %s\n\n",ov_comment(&vf,-1)->vendor);
//}
return true;
}
bool LLVorbisDecodeState::decodeSection()
{
if (!mInFilep)
{
LL_WARNS("AudioEngine") << "No cache file to decode in vorbis!" << LL_ENDL;
return true;
}
if (mDone)
{
// LL_WARNS("AudioEngine") << "Already done with decode, aborting!" << LL_ENDL;
return true;
}
char pcmout[4096]; /*Flawfinder: ignore*/
bool eof = false;
long ret=ov_read(&mVF, pcmout, sizeof(pcmout), 0, 2, 1, &mCurrentSection);
if (ret == 0)
{
/* EOF */
eof = true;
mDone = true;
mValid = true;
// LL_INFOS("AudioEngine") << "Vorbis EOF" << LL_ENDL;
}
else if (ret < 0)
{
/* error in the stream. Not a problem, just reporting it in
case we (the app) cares. In this case, we don't. */
LL_WARNS("AudioEngine") << "BAD vorbis decode in decodeSection." << LL_ENDL;
mValid = false;
mDone = true;
// We're done, return true.
return true;
}
else
{
// LL_INFOS("AudioEngine") << "Vorbis read " << ret << "bytes" << LL_ENDL;
/* we don't bother dealing with sample rate changes, etc, but.
you'll have to*/
std::copy(pcmout, pcmout+ret, std::back_inserter(mWAVBuffer));
}
return eof;
}
bool LLVorbisDecodeState::finishDecode()
{
if (!isValid())
{
LL_WARNS("AudioEngine") << "Bogus vorbis decode state for " << getUUID() << ", aborting!" << LL_ENDL;
return true; // We've finished
}
if (mFileHandle == LLLFSThread::nullHandle())
{
ov_clear(&mVF);
// write "data" chunk length, in little-endian format
S32 data_length = static_cast<S32>(mWAVBuffer.size()) - WAV_HEADER_SIZE;
mWAVBuffer[40] = (data_length) & 0x000000FF;
mWAVBuffer[41] = (data_length >> 8) & 0x000000FF;
mWAVBuffer[42] = (data_length >> 16) & 0x000000FF;
mWAVBuffer[43] = (data_length >> 24) & 0x000000FF;
// write overall "RIFF" length, in little-endian format
data_length += 36;
mWAVBuffer[4] = (data_length) & 0x000000FF;
mWAVBuffer[5] = (data_length >> 8) & 0x000000FF;
mWAVBuffer[6] = (data_length >> 16) & 0x000000FF;
mWAVBuffer[7] = (data_length >> 24) & 0x000000FF;
//
// FUDGECAKES!!! Vorbis encode/decode messes up loop point transitions (pop)
// do a cheap-and-cheesy crossfade
//
{
S16 *samplep;
S32 i;
S32 fade_length;
char pcmout[4096]; /*Flawfinder: ignore*/
fade_length = llmin((S32)128,(S32)(data_length-36)/8);
if((S32)mWAVBuffer.size() >= (WAV_HEADER_SIZE + 2* fade_length))
{
memcpy(pcmout, &mWAVBuffer[WAV_HEADER_SIZE], (2 * fade_length)); /*Flawfinder: ignore*/
}
llendianswizzle(&pcmout, 2, fade_length);
samplep = (S16 *)pcmout;
for (i = 0 ;i < fade_length; i++)
{
*samplep = llfloor((F32)*samplep * ((F32)i/(F32)fade_length));
samplep++;
}
llendianswizzle(&pcmout, 2, fade_length);
if((WAV_HEADER_SIZE+(2 * fade_length)) < (S32)mWAVBuffer.size())
{
memcpy(&mWAVBuffer[WAV_HEADER_SIZE], pcmout, (2 * fade_length)); /*Flawfinder: ignore*/
}
S32 near_end = static_cast<S32>(mWAVBuffer.size()) - (2 * fade_length);
if ((S32)mWAVBuffer.size() >= ( near_end + 2* fade_length))
{
memcpy(pcmout, &mWAVBuffer[near_end], (2 * fade_length)); /*Flawfinder: ignore*/
}
llendianswizzle(&pcmout, 2, fade_length);
samplep = (S16 *)pcmout;
for (i = fade_length-1 ; i >= 0; i--)
{
*samplep = llfloor((F32)*samplep * ((F32)i/(F32)fade_length));
samplep++;
}
llendianswizzle(&pcmout, 2, fade_length);
if (near_end + (2 * fade_length) < (S32)mWAVBuffer.size())
{
memcpy(&mWAVBuffer[near_end], pcmout, (2 * fade_length));/*Flawfinder: ignore*/
}
}
if (36 == data_length)
{
LL_WARNS("AudioEngine") << "BAD Vorbis decode in finishDecode!" << LL_ENDL;
mValid = false;
return true; // we've finished
}
mBytesRead = -1;
mFileHandle = LLLFSThread::sLocal->write(mOutFilename, &mWAVBuffer[0], 0, static_cast<S32>(mWAVBuffer.size()),
new WriteResponder(this));
}
if (mFileHandle != LLLFSThread::nullHandle())
{
if (mBytesRead >= 0)
{
if (mBytesRead == 0)
{
LL_WARNS("AudioEngine") << "Unable to write file in LLVorbisDecodeState::finishDecode" << LL_ENDL;
mValid = false;
return true; // we've finished
}
}
else
{
return false; // not done
}
}
mDone = true;
LL_DEBUGS("AudioEngine") << "Finished decode for " << getUUID() << LL_ENDL;
return true;
}
void LLVorbisDecodeState::flushBadFile()
{
if (mInFilep)
{
LL_WARNS("AudioEngine") << "Flushing bad vorbis file from cache for " << mUUID << LL_ENDL;
mInFilep->remove();
// <FS:ND> FIRE-15975; Delete the current file, or we might end with stale locks during the re-transfer
delete mInFilep;
mInFilep = NULL;
// </FS:ND>
}
}
//////////////////////////////////////////////////////////////////////////////
class LLAudioDecodeMgr::Impl
{
friend class LLAudioDecodeMgr;
Impl();
public:
void processQueue();
void startMoreDecodes();
void enqueueFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState>& decode_state);
void checkDecodesFinished();
protected:
std::deque<LLUUID> mDecodeQueue;
std::map<LLUUID, LLPointer<LLVorbisDecodeState>> mDecodes;
};
LLAudioDecodeMgr::Impl::Impl()
{
}
// Returns the in-progress decode_state, which may be an empty LLPointer if
// there was an error and there is no more work to be done.
LLPointer<LLVorbisDecodeState> beginDecodingAndWritingAudio(const LLUUID &decode_id);
// Return true if finished
bool tryFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState> decode_state);
void LLAudioDecodeMgr::Impl::processQueue()
{
// First, check if any audio from in-progress decodes are ready to play. If
// so, mark them ready for playback (or errored, in case of error).
checkDecodesFinished();
// Second, start as many decodes from the queue as permitted
startMoreDecodes();
}
void LLAudioDecodeMgr::Impl::startMoreDecodes()
{
llassert_always(gAudiop);
LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
// *NOTE: main_queue->postTo casts this refcounted smart pointer to a weak
// pointer
LL::WorkQueue::ptr_t general_queue = LL::WorkQueue::getInstance("General");
const LL::ThreadPool::ptr_t general_thread_pool = LL::ThreadPool::getInstance("General");
llassert_always(main_queue);
llassert_always(general_queue);
llassert_always(general_thread_pool);
// Set max decodes to double the thread count of the general work queue.
// This ensures the general work queue is full, but prevents theoretical
// buildup of buffers in memory due to disk writes once the
// LLVorbisDecodeState leaves the worker thread (see
// LLLFSThread::sLocal->write). This is probably as fast as we can get it
// without modifying/removing LLVorbisDecodeState, at which point we should
// consider decoding the audio during the asset download process.
// -Cosmic,2022-05-11
const size_t max_decodes = general_thread_pool->getWidth() * 2;
while (!mDecodeQueue.empty() && mDecodes.size() < max_decodes)
{
const LLUUID decode_id = mDecodeQueue.front();
mDecodeQueue.pop_front();
// Don't decode the same file twice
if (mDecodes.find(decode_id) != mDecodes.end())
{
continue;
}
if (gAudiop->hasDecodedFile(decode_id))
{
continue;
}
// Kick off a decode
mDecodes[decode_id] = LLPointer<LLVorbisDecodeState>(NULL);
bool posted = main_queue->postTo(
general_queue,
[decode_id]() // Work done on general queue
{
LLPointer<LLVorbisDecodeState> decode_state = beginDecodingAndWritingAudio(decode_id);
if (!decode_state)
{
if (gAudiop)
gAudiop->markSoundCorrupt(decode_id);
// Audio decode has errored
return decode_state;
}
// Disk write of decoded audio is now in progress off-thread
return decode_state;
},
[decode_id, this](LLPointer<LLVorbisDecodeState> decode_state) // Callback to main thread
mutable {
if (!gAudiop)
{
// There is no LLAudioEngine anymore. This might happen if
// an audio decode is enqueued just before shutdown.
return;
}
// At this point, we can be certain that the pointer to "this"
// is valid because the lifetime of "this" is dependent upon
// the lifetime of gAudiop.
enqueueFinishAudio(decode_id, decode_state);
});
if (! posted)
{
// Shutdown
// Consider making processQueue() do a cleanup instead
// of starting more decodes
LL_WARNS() << "Tried to start decoding on shutdown" << LL_ENDL;
}
}
}
LLPointer<LLVorbisDecodeState> beginDecodingAndWritingAudio(const LLUUID &decode_id)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA;
LL_DEBUGS() << "Decoding " << decode_id << " from audio queue!" << LL_ENDL;
// <FS:Ansariel> Sound cache
//std::string d_path = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, decode_id.asString()) + ".dsf";
std::string d_path = gDirUtilp->getExpandedFilename(LL_PATH_FS_SOUND_CACHE, decode_id.asString()) + ".dsf";
// </FS:Ansariel>
LLPointer<LLVorbisDecodeState> decode_state = new LLVorbisDecodeState(decode_id, d_path);
if (!decode_state->initDecode())
{
return NULL;
}
// Decode in a loop until we're done
while (!decode_state->decodeSection())
{
// decodeSection does all of the work above
}
if (!decode_state->isDone())
{
// Decode stopped early, or something bad happened to the file
// during decoding.
LL_WARNS("AudioEngine") << decode_id << " has invalid vorbis data or decode has been canceled, aborting decode" << LL_ENDL;
decode_state->flushBadFile();
return NULL;
}
if (!decode_state->isValid())
{
// We had an error when decoding, abort.
LL_WARNS("AudioEngine") << decode_id << " has invalid vorbis data, aborting decode" << LL_ENDL;
decode_state->flushBadFile();
return NULL;
}
// Kick off the writing of the decoded audio to the disk cache.
// The receiving thread can then cheaply call finishDecode() again to check
// if writing has finished. Someone has to hold on to the refcounted
// decode_state to prevent it from getting destroyed during write.
decode_state->finishDecode();
return decode_state;
}
void LLAudioDecodeMgr::Impl::enqueueFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState>& decode_state)
{
// Assumed fast
if (tryFinishAudio(decode_id, decode_state))
{
// Done early!
auto decode_iter = mDecodes.find(decode_id);
llassert(decode_iter != mDecodes.end());
mDecodes.erase(decode_iter);
return;
}
// Not done yet... enqueue it
mDecodes[decode_id] = decode_state;
}
void LLAudioDecodeMgr::Impl::checkDecodesFinished()
{
auto decode_iter = mDecodes.begin();
while (decode_iter != mDecodes.end())
{
const LLUUID& decode_id = decode_iter->first;
const LLPointer<LLVorbisDecodeState>& decode_state = decode_iter->second;
if (tryFinishAudio(decode_id, decode_state))
{
decode_iter = mDecodes.erase(decode_iter);
}
else
{
++decode_iter;
}
}
}
bool tryFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState> decode_state)
{
// decode_state is a file write in progress unless finished is true
bool finished = decode_state && decode_state->finishDecode();
if (!finished)
{
return false;
}
llassert_always(gAudiop);
LLAudioData *adp = gAudiop->getAudioData(decode_id);
if (!adp)
{
LL_WARNS("AudioEngine") << "Missing LLAudioData for decode of " << decode_id << LL_ENDL;
return true;
}
bool valid = decode_state && decode_state->isValid();
// Mark current decode finished regardless of success or failure
adp->setHasCompletedDecode(true);
// Flip flags for decoded data
adp->setHasDecodeFailed(!valid);
adp->setHasDecodedData(valid);
// When finished decoding, there will also be a decoded wav file cached on
// disk with the .dsf extension
if (valid)
{
adp->setHasWAVLoadFailed(false);
}
return true;
}
//////////////////////////////////////////////////////////////////////////////
LLAudioDecodeMgr::LLAudioDecodeMgr()
{
mImpl = new Impl();
}
LLAudioDecodeMgr::~LLAudioDecodeMgr()
{
delete mImpl;
mImpl = nullptr;
}
void LLAudioDecodeMgr::processQueue()
{
mImpl->processQueue();
}
bool LLAudioDecodeMgr::addDecodeRequest(const LLUUID &uuid)
{
// <FS:ND> Protect against corrupted sounds. Just do a quit exit instead of trying to decode over and over.
if (gAudiop && gAudiop->isCorruptSound(uuid))
return false;
// </FS:ND>
if (gAudiop && gAudiop->hasDecodedFile(uuid))
{
// Already have a decoded version, don't need to decode it.
LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " has decoded file already" << LL_ENDL;
return true;
}
if (gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND))
{
// Just put it on the decode queue it if it's not already in the queue
LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " has local asset file already" << LL_ENDL;
if (std::find(mImpl->mDecodeQueue.begin(), mImpl->mDecodeQueue.end(), uuid) == mImpl->mDecodeQueue.end())
{
mImpl->mDecodeQueue.emplace_back(uuid);
}
return true;
}
LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " no file available" << LL_ENDL;
return false;
}
+54
View File
@@ -0,0 +1,54 @@
/**
* @file llaudiodecodemgr.h
*
* $LicenseInfo:firstyear=2003&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$
*/
#ifndef LL_LLAUDIODECODEMGR_H
#define LL_LLAUDIODECODEMGR_H
#include "stdtypes.h"
#include "lluuid.h"
#include "llassettype.h"
#include "llframetimer.h"
#include "llsingleton.h"
template<class T> class LLPointer;
class LLVorbisDecodeState;
class LLAudioDecodeMgr : public LLSingleton<LLAudioDecodeMgr>
{
LLSINGLETON(LLAudioDecodeMgr);
~LLAudioDecodeMgr();
public:
void processQueue();
bool addDecodeRequest(const LLUUID &uuid);
void addAudioRequest(const LLUUID &uuid);
protected:
class Impl;
Impl* mImpl;
};
#endif
File diff suppressed because it is too large Load Diff
+575
View File
@@ -0,0 +1,575 @@
/**
* @file audioengine.h
* @brief Definition of LLAudioEngine base class abstracting the audio support
*
* $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$
*/
#ifndef LL_AUDIOENGINE_H
#define LL_AUDIOENGINE_H
#include <list>
#include <map>
#include <array>
#include "v3math.h"
#include "v3dmath.h"
#include "lltimer.h"
#include "lluuid.h"
#include "llframetimer.h"
#include "llassettype.h"
#include "llextendedstatus.h"
#include "lllistener.h"
#include <boost/signals2.hpp> // <FS:Ansariel> Output device selection
const F32 LL_WIND_UPDATE_INTERVAL = 0.1f;
const F32 LL_WIND_UNDERWATER_CENTER_FREQ = 20.f;
const F32 ATTACHED_OBJECT_TIMEOUT = 5.0f;
const F32 DEFAULT_MIN_DISTANCE = 2.0f;
#define LL_MAX_AUDIO_CHANNELS 30
// <FS:Ansariel> FIRE-4276: Increase number of audio buffers
//#define LL_MAX_AUDIO_BUFFERS 40 // Some extra for preloading, maybe?
#define LL_MAX_AUDIO_BUFFERS 60
// </FS:Ansariel>
class LLAudioSource;
class LLAudioData;
class LLAudioChannel;
class LLAudioChannelOpenAL;
class LLAudioBuffer;
class LLStreamingAudioInterface;
struct SoundData;
//
// LLAudioEngine definition
//
class LLAudioEngine
{
friend class LLAudioChannelOpenAL; // bleh. channel needs some listener methods.
public:
enum LLAudioType
{
AUDIO_TYPE_NONE = 0,
AUDIO_TYPE_SFX = 1,
AUDIO_TYPE_UI = 2,
AUDIO_TYPE_AMBIENT = 3,
AUDIO_TYPE_COUNT = 4 // last
};
enum LLAudioPlayState
{
// isInternetStreamPlaying() returns an *S32*, with
// 0 = stopped, 1 = playing, 2 = paused.
AUDIO_STOPPED = 0,
AUDIO_PLAYING = 1,
AUDIO_PAUSED = 2
};
LLAudioEngine();
virtual ~LLAudioEngine();
// initialization/startup/shutdown
virtual bool init(void *userdata, const std::string &app_title);
virtual std::string getDriverName(bool verbose) = 0;
virtual LLStreamingAudioInterface *createDefaultStreamingAudioImpl() const = 0;
virtual void shutdown();
// Used by the mechanics of the engine
//virtual void processQueue(const LLUUID &sound_guid);
virtual void setListener(LLVector3 pos,LLVector3 vel,LLVector3 up,LLVector3 at);
virtual void updateWind(LLVector3 direction, F32 camera_height_above_water) = 0;
virtual void idle();
virtual void updateChannels();
//
// "End user" functionality
//
virtual bool isWindEnabled();
virtual void enableWind(bool state_b);
// Use these for temporarily muting the audio system.
// Does not change buffers, initialization, etc. but
// stops playing new sounds.
void setMuted(bool muted);
bool getMuted() const { return mMuted; }
#ifdef USE_PLUGIN_MEDIA
LLPluginClassMedia* initializeMedia(const std::string& media_type);
#endif
F32 getMasterGain();
void setMasterGain(F32 gain);
F32 getSecondaryGain(S32 type);
void setSecondaryGain(S32 type, F32 gain);
F32 getInternetStreamGain();
virtual void setDopplerFactor(F32 factor);
virtual F32 getDopplerFactor();
virtual void setRolloffFactor(F32 factor);
virtual F32 getRolloffFactor();
virtual void setMaxWindGain(F32 gain);
// Methods actually related to setting up and removing sounds
// Owner ID is the owner of the object making the request
// NaCl - Sound Explorer
void triggerSound(const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain,
const S32 type = LLAudioEngine::AUDIO_TYPE_NONE,
const LLVector3d &pos_global = LLVector3d::zero,
// <FS:Testy> Optional parameter for setting the audio source UUID
// const LLUUID& source_object = LLUUID::null);
const LLUUID& source_object = LLUUID::null,
const LLUUID& audio_source_id = LLUUID::null);
// NaCl End
void triggerSound(SoundData& soundData);
bool preloadSound(const LLUUID &id);
void addAudioSource(LLAudioSource *asp);
void cleanupAudioSource(LLAudioSource *asp);
LLAudioSource *findAudioSource(const LLUUID &source_id);
LLAudioData *getAudioData(const LLUUID &audio_uuid);
// Internet stream implementation manipulation
LLStreamingAudioInterface *getStreamingAudioImpl();
void setStreamingAudioImpl(LLStreamingAudioInterface *impl);
// Internet stream methods - these will call down into the *mStreamingAudioImpl if it exists
void startInternetStream(const std::string& url);
void stopInternetStream();
void pauseInternetStream(S32 pause);
void updateInternetStream(); // expected to be called often
LLAudioPlayState isInternetStreamPlaying();
// use a value from 0.0 to 1.0, inclusive
void setInternetStreamGain(F32 vol);
std::string getInternetStreamURL();
// For debugging usage
virtual LLVector3 getListenerPos();
LLAudioBuffer *getFreeBuffer(); // Get a free buffer, or flush an existing one if you have to.
LLAudioChannel *getFreeChannel(const F32 priority); // Get a free channel or flush an existing one if your priority is higher
void cleanupBuffer(LLAudioBuffer *bufferp);
bool hasDecodedFile(const LLUUID &uuid);
bool hasLocalFile(const LLUUID &uuid);
bool updateBufferForData(LLAudioData *adp, const LLUUID &audio_uuid = LLUUID::null);
// <FS:Ansariel> Asset blacklisting
void removeAudioData(const LLUUID& audio_uuid);
// Asset callback when we're retrieved a sound from the asset server.
void startNextTransfer();
static void assetCallback(const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 result_code, LLExtStat ext_status);
// <FS:Ansariel> Output device selection
typedef std::map<LLUUID, std::string> output_device_map_t;
virtual output_device_map_t getDevices();
virtual void setDevice(const LLUUID& device_uuid) { };
typedef boost::signals2::signal<void(output_device_map_t output_device_map)> output_device_list_changed_callback_t;
boost::signals2::connection setOutputDeviceListChangedCallback(const output_device_list_changed_callback_t::slot_type& cb)
{
return mOutputDeviceListChangedCallback.connect(cb);
}
void OnOutputDeviceListChanged(output_device_map_t output_device_map);
// </FS:Ansariel>
friend class LLPipeline; // For debugging
public:
F32 mMaxWindGain; // Hack. Public to set before fade in?
protected:
virtual LLAudioBuffer *createBuffer() = 0;
virtual LLAudioChannel *createChannel() = 0;
virtual bool initWind() = 0;
virtual void cleanupWind() = 0;
virtual void setInternalGain(F32 gain) = 0;
void commitDeferredChanges();
virtual void allocateListener() = 0;
// listener methods
virtual void setListenerPos(LLVector3 vec);
virtual void setListenerVelocity(LLVector3 vec);
virtual void orientListener(LLVector3 up, LLVector3 at);
virtual void translateListener(LLVector3 vec);
F64 mapWindVecToGain(LLVector3 wind_vec);
F64 mapWindVecToPitch(LLVector3 wind_vec);
F64 mapWindVecToPan(LLVector3 wind_vec);
protected:
LLListener *mListenerp;
bool mMuted;
void* mUserData;
S32 mLastStatus;
bool mEnableWind;
LLUUID mCurrentTransfer; // Audio file currently being transferred by the system
LLFrameTimer mCurrentTransferTimer;
// A list of all audio sources that are known to the viewer at this time.
// This is most likely a superset of the ones that we actually have audio
// data for, or are playing back.
typedef std::map<LLUUID, LLAudioSource *> source_map;
typedef std::map<LLUUID, LLAudioData *> data_map;
source_map mAllSources;
data_map mAllData;
std::array<LLAudioChannel*, LL_MAX_AUDIO_CHANNELS> mChannels;
// Buffers needs to change into a different data structure, as the number of buffers
// that we have active should be limited by RAM usage, not count.
std::array<LLAudioBuffer*, LL_MAX_AUDIO_BUFFERS> mBuffers;
F32 mMasterGain;
F32 mInternalGain; // Actual gain set; either mMasterGain or 0 when mMuted is true.
F32 mSecondaryGain[AUDIO_TYPE_COUNT];
F32 mNextWindUpdate;
LLFrameTimer mWindUpdateTimer;
// <FS:Ansariel> Output device selection
output_device_list_changed_callback_t mOutputDeviceListChangedCallback;
private:
void setDefaults();
LLStreamingAudioInterface *mStreamingAudioImpl;
// <FS:ND> Protect against corrupted sounds
std::map<LLUUID,U32> mCorruptData;
public:
void markSoundCorrupt( LLUUID const & );
bool isCorruptSound( LLUUID const& ) const;
// </FS:ND>
};
//
// Standard audio source. Can be derived from for special sources, such as those attached to objects.
//
class LLAudioSource
{
public:
// owner_id is the id of the agent responsible for making this sound
// play, for example, the owner of the object currently playing it
// NaCl - Sound Explorer
LLAudioSource(const LLUUID &id, const LLUUID& owner_id, const F32 gain, const S32 type = LLAudioEngine::AUDIO_TYPE_NONE, const LLUUID& source_id = LLUUID::null, const bool isTrigger = true);
// NaCl End
virtual ~LLAudioSource();
virtual void update(); // Update this audio source
void updatePriority();
void preload(const LLUUID &audio_id); // Only used for preloading UI sounds, now.
void addAudioData(LLAudioData *adp, bool set_current = true);
void setForcedPriority(const bool ambient) { mForcedPriority = ambient; }
bool isForcedPriority() const { return mForcedPriority; }
void setLoop(const bool loop) { mLoop = loop; }
bool isLoop() const { return mLoop; }
void setSyncMaster(const bool master) { mSyncMaster = master; }
bool isSyncMaster() const { return mSyncMaster; }
void setSyncSlave(const bool slave) { mSyncSlave = slave; }
bool isSyncSlave() const { return mSyncSlave; }
void setQueueSounds(const bool queue) { mQueueSounds = queue; }
bool isQueueSounds() const { return mQueueSounds; }
void setPlayedOnce(const bool played_once) { mPlayedOnce = played_once; }
void setType(S32 type) { mType = type; }
S32 getType() { return mType; }
void setPositionGlobal(const LLVector3d &position_global) { mPositionGlobal = position_global; }
LLVector3d getPositionGlobal() const { return mPositionGlobal; }
LLVector3 getVelocity() const { return mVelocity; }
F32 getPriority() const { return mPriority; }
// Gain should always be clamped between 0 and 1.
F32 getGain() const { return mGain; }
virtual void setGain(const F32 gain) { mGain = llclamp(gain, 0.f, 1.f); }
const LLUUID &getID() const { return mID; }
// NaCl - Sound Explorer
const LLUUID &getLogID() const { return mLogID; }
// NaCl End
bool isDone() const;
bool isMuted() const { return mSourceMuted; }
LLAudioData *getCurrentData();
LLAudioData *getQueuedData();
LLAudioBuffer *getCurrentBuffer();
bool setupChannel();
// Stop the audio source, reset audio id even if muted
void stop();
// Start the audio source playing,
// takes mute into account to preserve previous id if nessesary
bool play(const LLUUID &audio_id);
bool hasPendingPreloads() const; // Has preloads that haven't been done yet
friend class LLAudioEngine;
friend class LLAudioChannel;
protected:
void setChannel(LLAudioChannel *channelp);
LLAudioChannel *getChannel() const { return mChannelp; }
// NaCl - Sound Explorer
static void logSoundPlay(const LLUUID& id, LLVector3d position, S32 type, const LLUUID& assetid, const LLUUID& ownerid, const LLUUID& sourceid, bool is_trigger, bool is_looped);
static void logSoundStop(const LLUUID& id);
static void pruneSoundLog();
static S32 sSoundHistoryPruneCounter;
// NaCl End
protected:
LLUUID mID; // The ID of the source is that of the object if it's attached to an object.
LLUUID mOwnerID; // owner of the object playing the sound
F32 mPriority;
F32 mGain;
bool mSourceMuted;
bool mForcedPriority; // ignore mute, set high priority, researved for sound preview and UI
bool mLoop;
bool mSyncMaster;
bool mSyncSlave;
bool mQueueSounds;
bool mPlayedOnce;
bool mCorrupted;
S32 mType;
LLVector3d mPositionGlobal;
LLVector3 mVelocity;
// NaCl - Sound explorer
LLUUID mLogID;
LLUUID mSourceID;
bool mIsTrigger;
// NaCl end
//LLAudioSource *mSyncMasterp; // If we're a slave, the source that we're synced to.
LLAudioChannel *mChannelp; // If we're currently playing back, this is the channel that we're assigned to.
LLAudioData *mCurrentDatap;
LLAudioData *mQueuedDatap;
typedef std::map<LLUUID, LLAudioData *> data_map;
data_map mPreloadMap;
LLFrameTimer mAgeTimer;
};
//
// Generic metadata about a particular piece of audio data.
// The actual data is handled by the derived LLAudioBuffer classes which are
// derived for each audio engine.
//
class LLAudioData
{
public:
LLAudioData(const LLUUID &uuid);
bool load();
LLUUID getID() const { return mID; }
LLAudioBuffer *getBuffer() const { return mBufferp; }
bool hasLocalData() const { return mHasLocalData; }
bool hasDecodedData() const { return mHasDecodedData; }
bool hasCompletedDecode() const { return mHasCompletedDecode; }
bool hasDecodeFailed() const { return mHasDecodeFailed; }
bool hasWAVLoadFailed() const { return mHasWAVLoadFailed; }
void setHasLocalData(const bool hld) { mHasLocalData = hld; }
void setHasDecodedData(const bool hdd) { mHasDecodedData = hdd; }
void setHasCompletedDecode(const bool hcd) { mHasCompletedDecode = hcd; }
void setHasDecodeFailed(const bool hdf) { mHasDecodeFailed = hdf; }
void setHasWAVLoadFailed(const bool hwlf) { mHasWAVLoadFailed = hwlf; }
friend class LLAudioEngine; // Severe laziness, bad.
protected:
LLUUID mID;
LLAudioBuffer *mBufferp; // If this data is being used by the audio system, a pointer to the buffer will be set here.
bool mHasLocalData; // Set true if the encoded sound asset file is available locally
bool mHasDecodedData; // Set true if the decoded sound file is available on disk
bool mHasCompletedDecode; // Set true when the sound is decoded
bool mHasDecodeFailed; // Set true if decoding failed, meaning the sound asset is bad
bool mHasWAVLoadFailed; // Set true if loading the decoded WAV file failed, meaning the sound asset should be decoded instead if
// possible
};
//
// Base class for an audio channel, i.e. a channel which is capable of playing back a sound.
// Management of channels is done generically, methods for actually manipulating the channel
// are derived for each audio engine.
//
class LLAudioChannel
{
public:
LLAudioChannel();
virtual ~LLAudioChannel();
virtual void setSource(LLAudioSource *sourcep);
LLAudioSource *getSource() const { return mCurrentSourcep; }
void setSecondaryGain(F32 gain) { mSecondaryGain = gain; }
F32 getSecondaryGain() { return mSecondaryGain; }
friend class LLAudioEngine;
friend class LLAudioSource;
protected:
virtual void play() = 0;
virtual void playSynced(LLAudioChannel *channelp) = 0;
virtual void cleanup() = 0;
virtual bool isPlaying() = 0;
void setWaiting(const bool waiting) { mWaiting = waiting; }
bool isWaiting() const { return mWaiting; }
virtual bool updateBuffer(); // Check to see if the buffer associated with the source changed, and update if necessary.
virtual void update3DPosition() = 0;
virtual void updateLoop() = 0; // Update your loop/completion status, for use by queueing/syncing.
protected:
LLAudioSource *mCurrentSourcep;
LLAudioBuffer *mCurrentBufferp;
bool mLoopedThisFrame;
bool mWaiting; // Waiting for sync.
F32 mSecondaryGain;
};
// Basically an interface class to the engine-specific implementation
// of audio data that's ready for playback.
// Will likely get more complex as we decide to do stuff like real streaming audio.
class LLAudioBuffer
{
public:
virtual ~LLAudioBuffer() {};
virtual bool loadWAV(const std::string& filename) = 0;
virtual U32 getLength() = 0;
friend class LLAudioEngine;
friend class LLAudioChannel;
friend class LLAudioData;
protected:
bool mInUse;
LLAudioData *mAudioDatap;
LLFrameTimer mLastUseTimer;
};
struct SoundData
{
LLUUID audio_uuid;
LLUUID owner_id;
F32 gain;
S32 type;
LLVector3d pos_global;
SoundData(const LLUUID &audio_uuid,
const LLUUID& owner_id,
const F32 gain,
const S32 type = LLAudioEngine::AUDIO_TYPE_NONE,
const LLVector3d &pos_global = LLVector3d::zero) :
audio_uuid(audio_uuid),
owner_id(owner_id),
gain(gain),
type(type),
pos_global(pos_global)
{
}
};
extern LLAudioEngine* gAudiop;
// NaCl - Sound explorer
struct LLSoundHistoryItem
{
LLUUID mID;
LLVector3d mPosition;
S32 mType;
bool mPlaying;
LLUUID mAssetID;
LLUUID mOwnerID;
LLUUID mSourceID;
bool mIsTrigger;
bool mIsLooped;
F64 mTimeStarted;
F64 mTimeStopped;
bool mReviewed;
bool mReviewedCollision;
LLSoundHistoryItem()
: mType(0)
, mPlaying(0)
, mIsTrigger(0)
, mIsLooped(0)
, mTimeStarted(0.f)
, mTimeStopped(0.f)
, mReviewed(false)
, mReviewedCollision(false)
{
}
};
extern std::map<LLUUID, LLSoundHistoryItem> gSoundHistory;
// NaCl End
#endif
+901
View File
@@ -0,0 +1,901 @@
/**
* @file audioengine_fmodstudio.cpp
* @brief Implementation of LLAudioEngine class abstracting the audio
* support as a FMODSTUDIO implementation
*
* $LicenseInfo:firstyear=2020&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2020, 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 "linden_common.h"
#include "llstreamingaudio.h"
#include "llstreamingaudio_fmodstudio.h"
#include "llaudioengine_fmodstudio.h"
#include "lllistener_fmodstudio.h"
#include "llerror.h"
#include "llmath.h"
#include "llrand.h"
#include "fmodstudio/fmod.hpp"
#include "fmodstudio/fmod_errors.h"
#include "lldir.h"
#include "llapr.h"
#include "sound_ids.h"
constexpr U32 EXTRA_SOUND_CHANNELS = 10;
FMOD_RESULT F_CALL windCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels);
FMOD::ChannelGroup *LLAudioEngine_FMODSTUDIO::mChannelGroups[LLAudioEngine::AUDIO_TYPE_COUNT] = {0};
static inline bool Check_FMOD_Error(FMOD_RESULT result, const char *string)
{
if (result == FMOD_OK)
{
return false;
}
if (result != FMOD_ERR_INVALID_HANDLE)
{
LL_WARNS("FMOD") << string << " Error: " << FMOD_ErrorString(result) << LL_ENDL;
}
else
{
LL_DEBUGS("FMOD") << string << " Error: " << FMOD_ErrorString(result) << LL_ENDL;
}
return true;
}
static LLUUID FMOD_GUID_to_LLUUID(FMOD_GUID guid)
{
return LLUUID(llformat("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", guid.Data1, guid.Data2, guid.Data3,
guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]));
}
static void set_device(FMOD::System* system, const LLUUID& device_uuid)
{
LL_INFOS() << "LLAudioEngine_FMODSTUDIO::setDevice with device_uuid=" << device_uuid << LL_ENDL;
int drivercount;
if (!Check_FMOD_Error(system->getNumDrivers(&drivercount), "FMOD::System::getNumDrivers") && drivercount > 0)
{
if (device_uuid.isNull())
{
LL_INFOS("FMOD") << "Setting driver \"Default\"" << LL_ENDL;
Check_FMOD_Error(system->setDriver(0), "FMOD::System::setDriver");
}
else
{
FMOD_GUID guid;
int r_samplerate, r_channels;
for (int i = 0; i < drivercount; ++i)
{
if (!Check_FMOD_Error(system->getDriverInfo(i, NULL, 0, &guid, &r_samplerate, NULL, &r_channels), "FMOD::System::getDriverInfo"))
{
LLUUID driver_guid = FMOD_GUID_to_LLUUID(guid);
if (driver_guid == device_uuid)
{
LL_INFOS("FMOD") << "Setting driver " << i << ": " << driver_guid << LL_ENDL;
Check_FMOD_Error(system->setDriver(i), "FMOD::System::setDriver");
return;
}
}
}
LL_INFOS("FMOD") << "Device not available (anymore) - falling back to default" << LL_ENDL;
Check_FMOD_Error(system->setDriver(0), "FMOD::System::setDriver");
}
}
}
FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE type, void *commanddata1, void *commanddata2, void* userdata)
{
FMOD::System* sys = (FMOD::System*)system;
LLAudioEngine_FMODSTUDIO* audio_engine = (LLAudioEngine_FMODSTUDIO*)userdata;
switch (type)
{
case FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED:
LL_DEBUGS("FMOD") << "FMOD system callback FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED" << LL_ENDL;
if (sys && audio_engine)
{
set_device(sys, audio_engine->getSelectedDeviceUUID());
audio_engine->OnOutputDeviceListChanged(audio_engine->getDevices());
}
break;
default:
break;
}
return FMOD_OK;
}
LLAudioEngine_FMODSTUDIO::LLAudioEngine_FMODSTUDIO(bool enable_profiler, U32 resample_method)
: mInited(false),
mWindGen(NULL),
mWindDSP(NULL),
mSystem(NULL),
mEnableProfiler(enable_profiler),
mWindDSPDesc(NULL),
mResampleMethod(resample_method),
mSelectedDeviceUUID()
{
}
LLAudioEngine_FMODSTUDIO::~LLAudioEngine_FMODSTUDIO()
{
// mWindDSPDesc, mWindGen and mWindDSP get cleaned up on cleanupWind in LLAudioEngine::shutdown()
// mSystem gets cleaned up at shutdown()
}
bool LLAudioEngine_FMODSTUDIO::init(void* userdata, const std::string &app_title)
{
U32 version;
FMOD_RESULT result;
LL_DEBUGS("AppInit") << "LLAudioEngine_FMODSTUDIO::init() initializing FMOD" << LL_ENDL;
result = FMOD::System_Create(&mSystem);
if (Check_FMOD_Error(result, "FMOD::System_Create"))
return false;
//will call LLAudioEngine_FMODSTUDIO::allocateListener, which needs a valid mSystem pointer.
LLAudioEngine::init(userdata, app_title);
result = mSystem->getVersion(&version);
Check_FMOD_Error(result, "FMOD::System::getVersion");
if (version < FMOD_VERSION)
{
LL_WARNS("AppInit") << "FMOD Studio version mismatch, actual: " << version
<< " expected:" << FMOD_VERSION << LL_ENDL;
}
// In this case, all sounds, PLUS wind and stream will be software.
result = mSystem->setSoftwareChannels(LL_MAX_AUDIO_CHANNELS + EXTRA_SOUND_CHANNELS);
Check_FMOD_Error(result, "FMOD::System::setSoftwareChannels");
Check_FMOD_Error(mSystem->setCallback(systemCallback), "FMOD::System::setCallback");
Check_FMOD_Error(mSystem->setUserData(this), "FMOD::System::setUserData");
FMOD_ADVANCEDSETTINGS settings = { };
settings.cbSize = sizeof(FMOD_ADVANCEDSETTINGS);
switch (mResampleMethod)
{
default:
case RESAMPLE_LINEAR:
settings.resamplerMethod = FMOD_DSP_RESAMPLER_LINEAR;
break;
case RESAMPLE_CUBIC:
settings.resamplerMethod = FMOD_DSP_RESAMPLER_CUBIC;
break;
case RESAMPLE_SPLINE:
settings.resamplerMethod = FMOD_DSP_RESAMPLER_SPLINE;
break;
}
result = mSystem->setAdvancedSettings(&settings);
Check_FMOD_Error(result, "FMOD::System::setAdvancedSettings");
// FMOD_INIT_THREAD_UNSAFE Disables thread safety for API calls.
// Only use this if FMOD is being called from a single thread, and if Studio API is not being used.
U32 fmod_flags = FMOD_INIT_NORMAL | FMOD_INIT_3D_RIGHTHANDED | FMOD_INIT_THREAD_UNSAFE;
if (mEnableProfiler)
{
fmod_flags |= FMOD_INIT_PROFILE_ENABLE;
}
#if LL_LINUX
bool audio_ok = false;
if (!audio_ok)
{
const char* env_string = getenv("LL_BAD_FMOD_PULSEAUDIO");
if (NULL == env_string)
{
LL_DEBUGS("AppInit") << "Trying PulseAudio audio output..." << LL_ENDL;
if ((result = mSystem->setOutput(FMOD_OUTPUTTYPE_PULSEAUDIO)) == FMOD_OK &&
(result = mSystem->init(LL_MAX_AUDIO_CHANNELS + EXTRA_SOUND_CHANNELS, fmod_flags, const_cast<char*>(app_title.c_str()))) == FMOD_OK)
{
LL_DEBUGS("AppInit") << "PulseAudio output initialized OKAY" << LL_ENDL;
audio_ok = true;
}
else
{
Check_FMOD_Error(result, "PulseAudio audio output FAILED to initialize");
}
}
else
{
LL_DEBUGS("AppInit") << "PulseAudio audio output SKIPPED" << LL_ENDL;
}
}
if (!audio_ok)
{
const char* env_string = getenv("LL_BAD_FMOD_ALSA");
if (NULL == env_string)
{
LL_DEBUGS("AppInit") << "Trying ALSA audio output..." << LL_ENDL;
if ((result = mSystem->setOutput(FMOD_OUTPUTTYPE_ALSA)) == FMOD_OK &&
(result = mSystem->init(LL_MAX_AUDIO_CHANNELS + EXTRA_SOUND_CHANNELS, fmod_flags, 0)) == FMOD_OK)
{
LL_DEBUGS("AppInit") << "ALSA audio output initialized OKAY" << LL_ENDL;
audio_ok = true;
}
else
{
Check_FMOD_Error(result, "ALSA audio output FAILED to initialize");
}
}
else
{
LL_DEBUGS("AppInit") << "ALSA audio output SKIPPED" << LL_ENDL;
}
}
if (!audio_ok)
{
LL_WARNS("AppInit") << "Overall audio init failure." << LL_ENDL;
return false;
}
// We're interested in logging which output method we
// ended up with, for QA purposes.
FMOD_OUTPUTTYPE output_type;
if (!Check_FMOD_Error(mSystem->getOutput(&output_type), "FMOD::System::getOutput"))
{
switch (output_type)
{
case FMOD_OUTPUTTYPE_NOSOUND:
LL_INFOS("AppInit") << "Audio output: NoSound" << LL_ENDL; break;
case FMOD_OUTPUTTYPE_PULSEAUDIO:
LL_INFOS("AppInit") << "Audio output: PulseAudio" << LL_ENDL; break;
case FMOD_OUTPUTTYPE_ALSA:
LL_INFOS("AppInit") << "Audio output: ALSA" << LL_ENDL; break;
default:
LL_INFOS("AppInit") << "Audio output: Unknown!" << LL_ENDL; break;
};
}
#else // LL_LINUX
// initialize the FMOD engine
// number of channel in this case looks to be identiacal to number of max simultaneously
// playing objects and we can set practically any number
result = mSystem->init(LL_MAX_AUDIO_CHANNELS + EXTRA_SOUND_CHANNELS, fmod_flags, 0);
if (Check_FMOD_Error(result, "Error initializing FMOD Studio with default settins, retrying with other format"))
{
result = mSystem->setSoftwareFormat(44100, FMOD_SPEAKERMODE_STEREO, 0/*- ignore*/);
if (Check_FMOD_Error(result, "Error setting sotware format. Can't init."))
{
return false;
}
result = mSystem->init(LL_MAX_AUDIO_CHANNELS + EXTRA_SOUND_CHANNELS, fmod_flags, 0);
}
if (Check_FMOD_Error(result, "Error initializing FMOD Studio"))
{
return false;
}
#endif
if (mEnableProfiler)
{
Check_FMOD_Error(mSystem->createChannelGroup("None", &mChannelGroups[AUDIO_TYPE_NONE]), "FMOD::System::createChannelGroup");
Check_FMOD_Error(mSystem->createChannelGroup("SFX", &mChannelGroups[AUDIO_TYPE_SFX]), "FMOD::System::createChannelGroup");
Check_FMOD_Error(mSystem->createChannelGroup("UI", &mChannelGroups[AUDIO_TYPE_UI]), "FMOD::System::createChannelGroup");
Check_FMOD_Error(mSystem->createChannelGroup("Ambient", &mChannelGroups[AUDIO_TYPE_AMBIENT]), "FMOD::System::createChannelGroup");
}
LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init() FMOD Studio initialized correctly" << LL_ENDL;
FMOD_ADVANCEDSETTINGS settings_dump = { };
mSystem->getAdvancedSettings(&settings_dump);
LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): resampler=" << settings_dump.resamplerMethod << " bytes" << LL_ENDL;
int r_numbuffers, r_samplerate, r_channels;
unsigned int r_bufferlength;
char r_name[512];
int latency = 100;
mSystem->getDSPBufferSize(&r_bufferlength, &r_numbuffers);
LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): r_bufferlength=" << r_bufferlength << " bytes" << LL_ENDL;
LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): r_numbuffers=" << r_numbuffers << LL_ENDL;
mSystem->getDriverInfo(0, r_name, 511, NULL, &r_samplerate, NULL, &r_channels);
r_name[511] = '\0';
LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): r_name=\"" << r_name << "\"" << LL_ENDL;
LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): r_samplerate=" << r_samplerate << "Hz" << LL_ENDL;
LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): r_channels=" << r_channels << LL_ENDL;
if (r_samplerate != 0)
latency = (int)(1000.0f * r_bufferlength * r_numbuffers / r_samplerate);
LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): latency=" << latency << "ms" << LL_ENDL;
mInited = true;
LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): initialization complete." << LL_ENDL;
getDevices(); // Purely to print out available devices for debugging reasons
return true;
}
//virtual
LLAudioEngine_FMODSTUDIO::output_device_map_t LLAudioEngine_FMODSTUDIO::getDevices()
{
output_device_map_t driver_map;
int drivercount;
int r_samplerate, r_channels;
char r_name[512];
FMOD_GUID guid;
if (!Check_FMOD_Error(mSystem->getNumDrivers(&drivercount), "FMOD::System::getNumDrivers"))
{
for (int i = 0; i < drivercount; ++i)
{
memset(r_name, 0, sizeof(r_name));
if (!Check_FMOD_Error(mSystem->getDriverInfo(i, r_name, 511, &guid, &r_samplerate, NULL, &r_channels), "FMOD::System::getDriverInfo"))
{
LLUUID driver_guid = FMOD_GUID_to_LLUUID(guid);
driver_map.insert(std::make_pair(driver_guid, r_name));
LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::getDevices(): r_name=\"" << r_name << "\" - guid: " << driver_guid << LL_ENDL;
}
}
}
return driver_map;
}
//virtual
void LLAudioEngine_FMODSTUDIO::setDevice(const LLUUID& device_uuid)
{
mSelectedDeviceUUID = device_uuid;
set_device(mSystem, device_uuid);
}
std::string LLAudioEngine_FMODSTUDIO::getDriverName(bool verbose)
{
llassert_always(mSystem);
if (verbose)
{
U32 version;
if (!Check_FMOD_Error(mSystem->getVersion(&version), "FMOD::System::getVersion"))
{
return llformat("FMOD Studio %1x.%02x.%02x", version >> 16, version >> 8 & 0x000000FF, version & 0x000000FF);
}
}
return "FMOD Studio";
}
// create our favourite FMOD-native streaming audio implementation
LLStreamingAudioInterface *LLAudioEngine_FMODSTUDIO::createDefaultStreamingAudioImpl() const
{
return new LLStreamingAudio_FMODSTUDIO(mSystem);
}
void LLAudioEngine_FMODSTUDIO::allocateListener(void)
{
try
{
mListenerp = (LLListener *) new LLListener_FMODSTUDIO(mSystem);
}
catch (const std::bad_alloc& e)
{
LL_WARNS("FMOD") << "Listener allocation failed due to: " << e.what() << LL_ENDL;
}
}
void LLAudioEngine_FMODSTUDIO::shutdown()
{
stopInternetStream();
LL_INFOS("FMOD") << "About to LLAudioEngine::shutdown()" << LL_ENDL;
LLAudioEngine::shutdown();
LL_INFOS("FMOD") << "LLAudioEngine_FMODSTUDIO::shutdown() closing FMOD Studio" << LL_ENDL;
if (mSystem)
{
Check_FMOD_Error(mSystem->close(), "FMOD::System::close");
Check_FMOD_Error(mSystem->release(), "FMOD::System::release");
}
LL_INFOS("FMOD") << "LLAudioEngine_FMODSTUDIO::shutdown() done closing FMOD Studio" << LL_ENDL;
delete mListenerp;
mListenerp = NULL;
}
LLAudioBuffer * LLAudioEngine_FMODSTUDIO::createBuffer()
{
return new LLAudioBufferFMODSTUDIO(mSystem);
}
LLAudioChannel * LLAudioEngine_FMODSTUDIO::createChannel()
{
return new LLAudioChannelFMODSTUDIO(mSystem);
}
bool LLAudioEngine_FMODSTUDIO::initWind()
{
mNextWindUpdate = 0.0;
if (!mWindDSPDesc)
{
mWindDSPDesc = new FMOD_DSP_DESCRIPTION();
}
if (!mWindDSP)
{
memset(mWindDSPDesc, 0, sizeof(*mWindDSPDesc)); //Set everything to zero
strncpy(mWindDSPDesc->name, "Wind Unit", sizeof(mWindDSPDesc->name));
mWindDSPDesc->pluginsdkversion = FMOD_PLUGIN_SDK_VERSION;
mWindDSPDesc->read = &windCallback; // Assign callback - may be called from arbitrary threads
if (Check_FMOD_Error(mSystem->createDSP(mWindDSPDesc, &mWindDSP), "FMOD::createDSP"))
return false;
if (mWindGen)
delete mWindGen;
int frequency = 44100;
FMOD_SPEAKERMODE mode;
if (Check_FMOD_Error(mSystem->getSoftwareFormat(&frequency, &mode, nullptr), "FMOD::System::getSoftwareFormat"))
{
cleanupWind();
return false;
}
mWindGen = new LLWindGen<MIXBUFFERFORMAT>((U32)frequency);
if (Check_FMOD_Error(mWindDSP->setUserData((void*)mWindGen), "FMOD::DSP::setUserData"))
{
cleanupWind();
return false;
}
if (Check_FMOD_Error(mWindDSP->setChannelFormat(FMOD_CHANNELMASK_STEREO, 2, mode), "FMOD::DSP::setChannelFormat"))
{
cleanupWind();
return false;
}
}
// *TODO: Should this guard against multiple plays?
if (Check_FMOD_Error(mSystem->playDSP(mWindDSP, nullptr, false, nullptr), "FMOD::System::playDSP"))
{
cleanupWind();
return false;
}
return true;
}
void LLAudioEngine_FMODSTUDIO::cleanupWind()
{
if (mWindDSP)
{
FMOD::ChannelGroup* master_group = NULL;
if (!Check_FMOD_Error(mSystem->getMasterChannelGroup(&master_group), "FMOD::System::getMasterChannelGroup")
&& master_group)
{
Check_FMOD_Error(master_group->removeDSP(mWindDSP), "FMOD::ChannelGroup::removeDSP");
}
Check_FMOD_Error(mWindDSP->release(), "FMOD::DSP::release");
mWindDSP = NULL;
}
delete mWindDSPDesc;
mWindDSPDesc = NULL;
delete mWindGen;
mWindGen = NULL;
}
//-----------------------------------------------------------------------
void LLAudioEngine_FMODSTUDIO::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));
mWindGen->mTargetFreq = (F32)center_freq;
mWindGen->mTargetGain = (F32)mapWindVecToGain(wind_vec) * mMaxWindGain;
mWindGen->mTargetPanGainR = (F32)mapWindVecToPan(wind_vec);
}
}
//-----------------------------------------------------------------------
void LLAudioEngine_FMODSTUDIO::setInternalGain(F32 gain)
{
if (!mInited)
{
return;
}
gain = llclamp(gain, 0.0f, 1.0f);
FMOD::ChannelGroup* master_group = NULL;
if (!Check_FMOD_Error(mSystem->getMasterChannelGroup(&master_group), "FMOD::System::getMasterChannelGroup")
&& master_group)
{
master_group->setVolume(gain);
}
LLStreamingAudioInterface *saimpl = getStreamingAudioImpl();
if (saimpl)
{
// fmod likes its streaming audio channel gain re-asserted after
// master volume change.
saimpl->setGain(saimpl->getGain());
}
}
//
// LLAudioChannelFMODSTUDIO implementation
//
LLAudioChannelFMODSTUDIO::LLAudioChannelFMODSTUDIO(FMOD::System *system) : LLAudioChannel(), mSystemp(system), mChannelp(NULL), mLastSamplePos(0)
{
}
LLAudioChannelFMODSTUDIO::~LLAudioChannelFMODSTUDIO()
{
cleanup();
}
bool LLAudioChannelFMODSTUDIO::updateBuffer()
{
if (!mCurrentSourcep)
{
// This channel isn't associated with any source, nothing
// to be updated
return false;
}
if (LLAudioChannel::updateBuffer())
{
// Base class update returned true, which means that we need to actually
// set up the channel for a different buffer.
LLAudioBufferFMODSTUDIO *bufferp = (LLAudioBufferFMODSTUDIO *)mCurrentSourcep->getCurrentBuffer();
// Grab the FMOD sample associated with the buffer
FMOD::Sound *soundp = bufferp->getSound();
if (!soundp)
{
// This is bad, there should ALWAYS be a sound associated with a legit
// buffer.
LL_ERRS() << "No FMOD sound!" << LL_ENDL;
return false;
}
// Actually play the sound. Start it off paused so we can do all the necessary
// setup.
if (!mChannelp)
{
FMOD_RESULT result = getSystem()->playSound(soundp, NULL /*free channel?*/, true, &mChannelp);
Check_FMOD_Error(result, "FMOD::System::playSound");
}
// Setting up channel mChannelID
}
// If we have a source for the channel, we need to update its gain.
if (mCurrentSourcep)
{
// SJB: warnings can spam and hurt framerate, disabling
//FMOD_RESULT result;
mChannelp->setVolume(getSecondaryGain() * mCurrentSourcep->getGain());
//Check_FMOD_Error(result, "FMOD::Channel::setVolume");
mChannelp->setMode(mCurrentSourcep->isLoop() ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF);
/*if(Check_FMOD_Error(result, "FMOD::Channel::setMode"))
{
S32 index;
mChannelp->getIndex(&index);
LL_WARNS() << "Channel " << index << "Source ID: " << mCurrentSourcep->getID()
<< " at " << mCurrentSourcep->getPositionGlobal() << LL_ENDL;
}*/
}
return true;
}
void LLAudioChannelFMODSTUDIO::update3DPosition()
{
if (!mChannelp)
{
// We're not actually a live channel (i.e., we're not playing back anything)
return;
}
LLAudioBufferFMODSTUDIO *bufferp = (LLAudioBufferFMODSTUDIO *)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->isForcedPriority())
{
// Prioritized UI and preview sounds don't need to do any positional updates.
set3DMode(false);
}
else
{
// Localized sound. Update the position and velocity of the sound.
set3DMode(true);
LLVector3 float_pos;
float_pos.setVec(mCurrentSourcep->getPositionGlobal());
FMOD_RESULT result = mChannelp->set3DAttributes((FMOD_VECTOR*)float_pos.mV, (FMOD_VECTOR*)mCurrentSourcep->getVelocity().mV);
Check_FMOD_Error(result, "FMOD::Channel::set3DAttributes");
}
}
void LLAudioChannelFMODSTUDIO::updateLoop()
{
if (!mChannelp)
{
// May want to clear up the loop/sample counters.
return;
}
//
// Hack: We keep track of whether we looped or not by seeing when the
// sample position looks like it's going backwards. Not reliable; may
// yield false negatives.
//
U32 cur_pos;
Check_FMOD_Error(mChannelp->getPosition(&cur_pos, FMOD_TIMEUNIT_PCMBYTES), "FMOD::Channel::getPosition");
if (cur_pos < (U32)mLastSamplePos)
{
mLoopedThisFrame = true;
}
mLastSamplePos = cur_pos;
}
void LLAudioChannelFMODSTUDIO::cleanup()
{
if (!mChannelp)
{
// Aborting cleanup with no channel handle.
return;
}
//Cleaning up channel mChannelID
Check_FMOD_Error(mChannelp->stop(), "FMOD::Channel::stop");
mCurrentBufferp = NULL;
mChannelp = NULL;
}
void LLAudioChannelFMODSTUDIO::play()
{
if (!mChannelp)
{
LL_WARNS() << "Playing without a channel handle, aborting" << LL_ENDL;
return;
}
Check_FMOD_Error(mChannelp->setPaused(false), "FMOD::Channel::setPaused");
getSource()->setPlayedOnce(true);
if (LLAudioEngine_FMODSTUDIO::mChannelGroups[getSource()->getType()])
Check_FMOD_Error(mChannelp->setChannelGroup(LLAudioEngine_FMODSTUDIO::mChannelGroups[getSource()->getType()]), "FMOD::Channel::setChannelGroup");
}
void LLAudioChannelFMODSTUDIO::playSynced(LLAudioChannel *channelp)
{
LLAudioChannelFMODSTUDIO *fmod_channelp = (LLAudioChannelFMODSTUDIO*)channelp;
if (!(fmod_channelp->mChannelp && mChannelp))
{
// Don't have channels allocated to both the master and the slave
return;
}
U32 cur_pos;
if (Check_FMOD_Error(mChannelp->getPosition(&cur_pos, FMOD_TIMEUNIT_PCMBYTES), "Unable to retrieve current position"))
return;
cur_pos %= mCurrentBufferp->getLength();
// Try to match the position of our sync master
Check_FMOD_Error(mChannelp->setPosition(cur_pos, FMOD_TIMEUNIT_PCMBYTES), "Unable to set current position");
// Start us playing
play();
}
bool LLAudioChannelFMODSTUDIO::isPlaying()
{
if (!mChannelp)
{
return false;
}
bool paused, playing;
Check_FMOD_Error(mChannelp->getPaused(&paused),"FMOD::Channel::getPaused");
Check_FMOD_Error(mChannelp->isPlaying(&playing),"FMOD::Channel::isPlaying");
return !paused && playing;
}
//
// LLAudioChannelFMODSTUDIO implementation
//
LLAudioBufferFMODSTUDIO::LLAudioBufferFMODSTUDIO(FMOD::System *system) : mSystemp(system), mSoundp(NULL)
{
}
LLAudioBufferFMODSTUDIO::~LLAudioBufferFMODSTUDIO()
{
if (mSoundp)
{
Check_FMOD_Error(mSoundp->release(), "FMOD::Sound::Release");
mSoundp = NULL;
}
}
bool LLAudioBufferFMODSTUDIO::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;
}
if (!gDirUtilp->fileExists(filename))
{
// File not found, abort.
return false;
}
if (mSoundp)
{
// If there's already something loaded in this buffer, clean it up.
Check_FMOD_Error(mSoundp->release(), "FMOD::Sound::release");
mSoundp = NULL;
}
FMOD_MODE base_mode = FMOD_LOOP_NORMAL;
FMOD_CREATESOUNDEXINFO exinfo;
memset(&exinfo, 0, sizeof(exinfo));
exinfo.cbsize = sizeof(exinfo);
exinfo.suggestedsoundtype = FMOD_SOUND_TYPE_WAV; //Hint to speed up loading.
// Load up the wav file into an fmod sample (since 1.05 fmod studio expects everything in UTF-8)
FMOD_RESULT result = getSystem()->createSound(filename.c_str(), base_mode, &exinfo, &mSoundp);
if (result != FMOD_OK)
{
// We failed to load the file for some reason.
LL_WARNS() << "Could not load data '" << filename << "': " << FMOD_ErrorString(result) << LL_ENDL;
//
// 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 LLAudioBufferFMODSTUDIO::getLength()
{
if (!mSoundp)
{
return 0;
}
U32 length;
Check_FMOD_Error(mSoundp->getLength(&length, FMOD_TIMEUNIT_PCMBYTES), "FMOD::Sound::getLength");
return length;
}
void LLAudioChannelFMODSTUDIO::set3DMode(bool use3d)
{
FMOD_MODE current_mode;
if (Check_FMOD_Error(mChannelp->getMode(&current_mode), "FMOD::Channel::getMode"))
return;
FMOD_MODE new_mode = current_mode;
new_mode &= ~(use3d ? FMOD_2D : FMOD_3D);
new_mode |= use3d ? FMOD_3D : FMOD_2D;
if (current_mode != new_mode)
{
Check_FMOD_Error(mChannelp->setMode(new_mode), "FMOD::Channel::setMode");
}
}
// *NOTE: This is almost certainly being called on the mixer thread,
// not the main thread. May have implications for callees or audio
// engine shutdown.
FMOD_RESULT F_CALL windCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels)
{
// inbuffer = fmod's original mixbuffer.
// outbuffer = the buffer passed from the previous DSP unit.
// length = length in samples at this mix time.
LLWindGen<LLAudioEngine_FMODSTUDIO::MIXBUFFERFORMAT> *windgen = NULL;
FMOD::DSP *thisdsp = (FMOD::DSP *)dsp_state->instance;
thisdsp->getUserData((void **)&windgen);
if (windgen)
{
windgen->windGenerate((LLAudioEngine_FMODSTUDIO::MIXBUFFERFORMAT *)outbuffer, length);
}
return FMOD_OK;
}
+146
View File
@@ -0,0 +1,146 @@
/**
* @file audioengine_fmodstudio.h
* @brief Definition of LLAudioEngine class abstracting the audio
* support as a FMODSTUDIO implementation
*
* $LicenseInfo:firstyear=2020&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2020, 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$
*/
#ifndef LL_AUDIOENGINE_FMODSTUDIO_H
#define LL_AUDIOENGINE_FMODSTUDIO_H
#include "llaudioengine.h"
#include "llwindgen.h"
//Stubs
class LLAudioStreamManagerFMODSTUDIO;
namespace FMOD
{
class System;
class Channel;
class ChannelGroup;
class Sound;
class DSP;
}
typedef struct FMOD_DSP_DESCRIPTION FMOD_DSP_DESCRIPTION;
//Interfaces
class LLAudioEngine_FMODSTUDIO : public LLAudioEngine
{
public:
enum
{
RESAMPLE_LINEAR=0,
RESAMPLE_CUBIC,
RESAMPLE_SPLINE
};
LLAudioEngine_FMODSTUDIO(bool enable_profiler, U32 resample_method);
virtual ~LLAudioEngine_FMODSTUDIO();
// initialization/startup/shutdown
virtual bool init(void *user_data, const std::string &app_title);
virtual std::string getDriverName(bool verbose);
virtual LLStreamingAudioInterface* createDefaultStreamingAudioImpl() const;
virtual void allocateListener();
virtual void shutdown();
/*virtual*/ bool initWind();
/*virtual*/ void cleanupWind();
/*virtual*/void updateWind(LLVector3 direction, F32 camera_height_above_water);
typedef F32 MIXBUFFERFORMAT;
FMOD::System *getSystem() const {return mSystem;}
/*virtual*/ std::map<LLUUID, std::string> getDevices();
/*virtual*/ void setDevice(const LLUUID& device_uuid);
LLUUID getSelectedDeviceUUID() const { return mSelectedDeviceUUID; }
protected:
/*virtual*/ LLAudioBuffer *createBuffer(); // Get a free buffer, or flush an existing one if you have to.
/*virtual*/ LLAudioChannel *createChannel(); // Create a new audio channel.
/*virtual*/ void setInternalGain(F32 gain);
bool mInited;
LLWindGen<MIXBUFFERFORMAT> *mWindGen;
FMOD_DSP_DESCRIPTION *mWindDSPDesc;
FMOD::DSP *mWindDSP;
FMOD::System *mSystem;
bool mEnableProfiler;
U32 mResampleMethod;
LLUUID mSelectedDeviceUUID;
public:
static FMOD::ChannelGroup *mChannelGroups[LLAudioEngine::AUDIO_TYPE_COUNT];
};
class LLAudioChannelFMODSTUDIO : public LLAudioChannel
{
public:
LLAudioChannelFMODSTUDIO(FMOD::System *audioengine);
virtual ~LLAudioChannelFMODSTUDIO();
protected:
/*virtual*/ void play();
/*virtual*/ void playSynced(LLAudioChannel *channelp);
/*virtual*/ void cleanup();
/*virtual*/ bool isPlaying();
/*virtual*/ bool updateBuffer();
/*virtual*/ void update3DPosition();
/*virtual*/ void updateLoop();
void set3DMode(bool use3d);
protected:
FMOD::System *getSystem() const {return mSystemp;}
FMOD::System *mSystemp;
FMOD::Channel *mChannelp;
S32 mLastSamplePos;
};
class LLAudioBufferFMODSTUDIO : public LLAudioBuffer
{
public:
LLAudioBufferFMODSTUDIO(FMOD::System *audioengine);
virtual ~LLAudioBufferFMODSTUDIO();
/*virtual*/ bool loadWAV(const std::string& filename);
/*virtual*/ U32 getLength();
friend class LLAudioChannelFMODSTUDIO;
protected:
FMOD::System *getSystem() const {return mSystemp;}
FMOD::System *mSystemp;
FMOD::Sound *getSound() const{ return mSoundp; }
FMOD::Sound *mSoundp;
};
#endif // LL_AUDIOENGINE_FMODSTUDIO_H
+570
View File
@@ -0,0 +1,570 @@
/**
* @file audioengine_openal.cpp
* @brief implementation of audio engine using OpenAL
* support as a OpenAL 3D implementation
*
* $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 "linden_common.h"
#include "lldir.h"
#include "llaudioengine_openal.h"
#include "lllistener_openal.h"
const float LLAudioEngine_OpenAL::WIND_BUFFER_SIZE_SEC = 0.05f;
LLAudioEngine_OpenAL::LLAudioEngine_OpenAL()
:
mWindGen(NULL),
mWindBuf(NULL),
mWindBufFreq(0),
mWindBufSamples(0),
mWindBufBytes(0),
mWindSource(AL_NONE),
mNumEmptyWindALBuffers(MAX_NUM_WIND_BUFFERS)
{
}
// virtual
LLAudioEngine_OpenAL::~LLAudioEngine_OpenAL()
{
}
// virtual
bool LLAudioEngine_OpenAL::init(void* userdata, const std::string &app_title)
{
mWindGen = NULL;
LLAudioEngine::init(userdata, app_title);
if(!alutInit(NULL, NULL))
{
LL_WARNS() << "LLAudioEngine_OpenAL::init() ALUT initialization failed: " << alutGetErrorString (alutGetError ()) << LL_ENDL;
return false;
}
LL_INFOS() << "LLAudioEngine_OpenAL::init() OpenAL successfully initialized" << LL_ENDL;
LL_INFOS() << "OpenAL version: "
<< ll_safe_string(alGetString(AL_VERSION)) << LL_ENDL;
LL_INFOS() << "OpenAL vendor: "
<< ll_safe_string(alGetString(AL_VENDOR)) << LL_ENDL;
LL_INFOS() << "OpenAL renderer: "
<< ll_safe_string(alGetString(AL_RENDERER)) << LL_ENDL;
ALint major = alutGetMajorVersion ();
ALint minor = alutGetMinorVersion ();
LL_INFOS() << "ALUT version: " << major << "." << minor << LL_ENDL;
ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext());
alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &major);
alcGetIntegerv(device, ALC_MINOR_VERSION, 1, &minor);
LL_INFOS() << "ALC version: " << major << "." << minor << LL_ENDL;
LL_INFOS() << "ALC default device: "
<< ll_safe_string(alcGetString(device,
ALC_DEFAULT_DEVICE_SPECIFIER))
<< LL_ENDL;
return true;
}
// virtual
std::string LLAudioEngine_OpenAL::getDriverName(bool verbose)
{
ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext());
std::ostringstream version;
version <<
"OpenAL";
if (verbose)
{
version <<
", version " <<
ll_safe_string(alGetString(AL_VERSION)) <<
" / " <<
ll_safe_string(alGetString(AL_VENDOR)) <<
" / " <<
ll_safe_string(alGetString(AL_RENDERER));
if (device)
version <<
": " <<
ll_safe_string(alcGetString(device,
ALC_DEFAULT_DEVICE_SPECIFIER));
}
return version.str();
}
// virtual
void LLAudioEngine_OpenAL::allocateListener()
{
mListenerp = (LLListener *) new LLListener_OpenAL();
if(!mListenerp)
{
LL_WARNS() << "LLAudioEngine_OpenAL::allocateListener() Listener creation failed" << LL_ENDL;
}
}
// virtual
void LLAudioEngine_OpenAL::shutdown()
{
LL_INFOS() << "About to LLAudioEngine::shutdown()" << LL_ENDL;
LLAudioEngine::shutdown();
// If a subsequent error occurs while there is still an error recorded
// internally, the second error will simply be ignored.
// Clear previous error to make sure we will captuare a valid failure reason
ALenum error = alutGetError();
if (error != ALUT_ERROR_NO_ERROR)
{
LL_WARNS() << "Uncleared error state prior to shutdown: "
<< alutGetErrorString(error) << LL_ENDL;
}
LL_INFOS() << "About to alutExit()" << LL_ENDL;
if(!alutExit())
{
LL_WARNS() << "LLAudioEngine_OpenAL::shutdown() ALUT shutdown failed: " << alutGetErrorString (alutGetError ()) << LL_ENDL;
}
LL_INFOS() << "LLAudioEngine_OpenAL::shutdown() OpenAL successfully shut down" << LL_ENDL;
delete mListenerp;
mListenerp = NULL;
}
LLAudioBuffer *LLAudioEngine_OpenAL::createBuffer()
{
return new LLAudioBufferOpenAL();
}
LLAudioChannel *LLAudioEngine_OpenAL::createChannel()
{
return new LLAudioChannelOpenAL();
}
void LLAudioEngine_OpenAL::setInternalGain(F32 gain)
{
//LL_INFOS() << "LLAudioEngine_OpenAL::setInternalGain() Gain: " << gain << LL_ENDL;
alListenerf(AL_GAIN, gain);
}
LLAudioChannelOpenAL::LLAudioChannelOpenAL()
:
mALSource(AL_NONE),
mLastSamplePos(0)
{
alGenSources(1, &mALSource);
}
LLAudioChannelOpenAL::~LLAudioChannelOpenAL()
{
cleanup();
alDeleteSources(1, &mALSource);
}
void LLAudioChannelOpenAL::cleanup()
{
alSourceStop(mALSource);
alSourcei(mALSource, AL_BUFFER, AL_NONE);
mCurrentBufferp = NULL;
}
void LLAudioChannelOpenAL::play()
{
if (mALSource == AL_NONE)
{
LL_WARNS() << "Playing without a mALSource, aborting" << LL_ENDL;
return;
}
if(!isPlaying())
{
alSourcePlay(mALSource);
getSource()->setPlayedOnce(true);
}
}
void LLAudioChannelOpenAL::playSynced(LLAudioChannel *channelp)
{
if (channelp)
{
LLAudioChannelOpenAL *masterchannelp =
(LLAudioChannelOpenAL*)channelp;
if (mALSource != AL_NONE &&
masterchannelp->mALSource != AL_NONE)
{
// we have channels allocated to master and slave
ALfloat master_offset;
alGetSourcef(masterchannelp->mALSource, AL_SEC_OFFSET,
&master_offset);
LL_INFOS() << "Syncing with master at " << master_offset
<< "sec" << LL_ENDL;
// *TODO: detect when this fails, maybe use AL_SAMPLE_
alSourcef(mALSource, AL_SEC_OFFSET, master_offset);
}
}
play();
}
bool LLAudioChannelOpenAL::isPlaying()
{
if (mALSource != AL_NONE)
{
ALint state;
alGetSourcei(mALSource, AL_SOURCE_STATE, &state);
if(state == AL_PLAYING)
{
return true;
}
}
return false;
}
bool LLAudioChannelOpenAL::updateBuffer()
{
if (!mCurrentSourcep)
{
// This channel isn't associated with any source, nothing
// to be updated
return false;
}
if (LLAudioChannel::updateBuffer())
{
// Base class update returned true, which means that we need to actually
// set up the source for a different buffer.
LLAudioBufferOpenAL *bufferp = (LLAudioBufferOpenAL *)mCurrentSourcep->getCurrentBuffer();
ALuint buffer = bufferp->getBuffer();
alSourcei(mALSource, AL_BUFFER, buffer);
mLastSamplePos = 0;
}
if (mCurrentSourcep)
{
alSourcef(mALSource, AL_GAIN,
mCurrentSourcep->getGain() * getSecondaryGain());
alSourcei(mALSource, AL_LOOPING,
mCurrentSourcep->isLoop() ? AL_TRUE : AL_FALSE);
alSourcef(mALSource, AL_ROLLOFF_FACTOR,
gAudiop->mListenerp->getRolloffFactor());
}
return true;
}
void LLAudioChannelOpenAL::updateLoop()
{
if (mALSource == AL_NONE)
{
return;
}
// Hack: We keep track of whether we looped or not by seeing when the
// sample position looks like it's going backwards. Not reliable; may
// yield false negatives.
//
ALint cur_pos;
alGetSourcei(mALSource, AL_SAMPLE_OFFSET, &cur_pos);
if (cur_pos < mLastSamplePos)
{
mLoopedThisFrame = true;
}
mLastSamplePos = cur_pos;
}
void LLAudioChannelOpenAL::update3DPosition()
{
if(!mCurrentSourcep)
{
return;
}
if (mCurrentSourcep->isForcedPriority())
{
alSource3f(mALSource, AL_POSITION, 0.0, 0.0, 0.0);
alSource3f(mALSource, AL_VELOCITY, 0.0, 0.0, 0.0);
alSourcei (mALSource, AL_SOURCE_RELATIVE, AL_TRUE);
} else {
LLVector3 float_pos;
float_pos.setVec(mCurrentSourcep->getPositionGlobal());
alSourcefv(mALSource, AL_POSITION, float_pos.mV);
alSourcefv(mALSource, AL_VELOCITY, mCurrentSourcep->getVelocity().mV);
alSourcei (mALSource, AL_SOURCE_RELATIVE, AL_FALSE);
}
alSourcef(mALSource, AL_GAIN, mCurrentSourcep->getGain() * getSecondaryGain());
}
LLAudioBufferOpenAL::LLAudioBufferOpenAL()
{
mALBuffer = AL_NONE;
}
LLAudioBufferOpenAL::~LLAudioBufferOpenAL()
{
cleanup();
}
void LLAudioBufferOpenAL::cleanup()
{
if(mALBuffer != AL_NONE)
{
alGetError(); // <ND/>
alDeleteBuffers(1, &mALBuffer);
// <FS:ND> Print warning on possible leak.
ALenum error = alGetError();
if( AL_NO_ERROR != error )
LL_WARNS() << "openal error: " << error << " possible memory leak hit" << LL_ENDL;
// </FS:ND>
mALBuffer = AL_NONE;
}
}
bool LLAudioBufferOpenAL::loadWAV(const std::string& filename)
{
cleanup();
mALBuffer = alutCreateBufferFromFile(filename.c_str());
if(mALBuffer == AL_NONE)
{
ALenum error = alutGetError();
if (gDirUtilp->fileExists(filename))
{
LL_WARNS() <<
"LLAudioBufferOpenAL::loadWAV() Error loading "
<< filename
<< " " << alutGetErrorString(error) << LL_ENDL;
}
else
{
// It's common for the file to not actually exist.
LL_DEBUGS() <<
"LLAudioBufferOpenAL::loadWAV() Error loading "
<< filename
<< " " << alutGetErrorString(error) << LL_ENDL;
}
return false;
}
return true;
}
U32 LLAudioBufferOpenAL::getLength()
{
if(mALBuffer == AL_NONE)
{
return 0;
}
ALint length;
alGetBufferi(mALBuffer, AL_SIZE, &length);
return length / 2; // convert size in bytes to size in (16-bit) samples
}
// ------------
bool LLAudioEngine_OpenAL::initWind()
{
ALenum error;
LL_INFOS() << "LLAudioEngine_OpenAL::initWind() start" << LL_ENDL;
mNumEmptyWindALBuffers = MAX_NUM_WIND_BUFFERS;
alGetError(); /* clear error */
alGenSources(1,&mWindSource);
if((error=alGetError()) != AL_NO_ERROR)
{
LL_WARNS() << "LLAudioEngine_OpenAL::initWind() Error creating wind sources: "<<error<<LL_ENDL;
}
mWindGen = new LLWindGen<WIND_SAMPLE_T>;
mWindBufFreq = mWindGen->getInputSamplingRate();
mWindBufSamples = llceil(mWindBufFreq * WIND_BUFFER_SIZE_SEC);
mWindBufBytes = mWindBufSamples * 2 /*stereo*/ * sizeof(WIND_SAMPLE_T);
mWindBuf = new WIND_SAMPLE_T [mWindBufSamples * 2 /*stereo*/];
if(mWindBuf==NULL)
{
LL_ERRS() << "LLAudioEngine_OpenAL::initWind() Error creating wind memory buffer" << LL_ENDL;
return false;
}
LL_INFOS() << "LLAudioEngine_OpenAL::initWind() done" << LL_ENDL;
return true;
}
void LLAudioEngine_OpenAL::cleanupWind()
{
LL_INFOS() << "LLAudioEngine_OpenAL::cleanupWind()" << LL_ENDL;
if (mWindSource != AL_NONE)
{
// detach and delete all outstanding buffers on the wind source
alSourceStop(mWindSource);
ALint processed;
alGetSourcei(mWindSource, AL_BUFFERS_PROCESSED, &processed);
while (processed--)
{
ALuint buffer = AL_NONE;
alSourceUnqueueBuffers(mWindSource, 1, &buffer);
alDeleteBuffers(1, &buffer);
}
// delete the wind source itself
alDeleteSources(1, &mWindSource);
mWindSource = AL_NONE;
}
delete[] mWindBuf;
mWindBuf = NULL;
delete mWindGen;
mWindGen = NULL;
}
void LLAudioEngine_OpenAL::updateWind(LLVector3 wind_vec, F32 camera_altitude)
{
LLVector3 wind_pos;
F64 pitch;
F64 center_freq;
ALenum error;
if (!mEnableWind)
return;
if(!mWindBuf)
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]);
pitch = 1.0 + mapWindVecToPitch(wind_vec);
center_freq = 80.0 * pow(pitch,2.5*(mapWindVecToGain(wind_vec)+1.0));
mWindGen->mTargetFreq = (F32)center_freq;
mWindGen->mTargetGain = (F32)mapWindVecToGain(wind_vec) * mMaxWindGain;
mWindGen->mTargetPanGainR = (F32)mapWindVecToPan(wind_vec);
alSourcei(mWindSource, AL_LOOPING, AL_FALSE);
alSource3f(mWindSource, AL_POSITION, 0.0, 0.0, 0.0);
alSource3f(mWindSource, AL_VELOCITY, 0.0, 0.0, 0.0);
alSourcef(mWindSource, AL_ROLLOFF_FACTOR, 0.0);
alSourcei(mWindSource, AL_SOURCE_RELATIVE, AL_TRUE);
}
// ok lets make a wind buffer now
ALint processed, queued, unprocessed;
alGetSourcei(mWindSource, AL_BUFFERS_PROCESSED, &processed);
alGetSourcei(mWindSource, AL_BUFFERS_QUEUED, &queued);
unprocessed = queued - processed;
// ensure that there are always at least 3x as many filled buffers
// queued as we managed to empty since last time.
mNumEmptyWindALBuffers = llmin(mNumEmptyWindALBuffers + processed * 3 - unprocessed, MAX_NUM_WIND_BUFFERS-unprocessed);
mNumEmptyWindALBuffers = llmax(mNumEmptyWindALBuffers, 0);
//LL_INFOS() << "mNumEmptyWindALBuffers: " << mNumEmptyWindALBuffers <<" (" << unprocessed << ":" << processed << ")" << LL_ENDL;
while(processed--) // unqueue old buffers
{
ALuint buffer;
ALenum error;
alGetError(); /* clear error */
alSourceUnqueueBuffers(mWindSource, 1, &buffer);
error = alGetError();
if(error != AL_NO_ERROR)
{
LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() error swapping (unqueuing) buffers" << LL_ENDL;
}
else
{
alDeleteBuffers(1, &buffer);
}
}
unprocessed += mNumEmptyWindALBuffers;
while (mNumEmptyWindALBuffers > 0) // fill+queue new buffers
{
ALuint buffer;
alGetError(); /* clear error */
alGenBuffers(1,&buffer);
if((error=alGetError()) != AL_NO_ERROR)
{
LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() Error creating wind buffer: " << error << LL_ENDL;
break;
}
alBufferData(buffer,
AL_FORMAT_STEREO_FLOAT32,
mWindGen->windGenerate(mWindBuf,
mWindBufSamples),
mWindBufBytes,
mWindBufFreq);
error = alGetError();
if(error != AL_NO_ERROR)
{
LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() error swapping (bufferdata) buffers" << LL_ENDL;
}
alSourceQueueBuffers(mWindSource, 1, &buffer);
error = alGetError();
if(error != AL_NO_ERROR)
{
LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() error swapping (queuing) buffers" << LL_ENDL;
}
--mNumEmptyWindALBuffers;
}
ALint playing;
alGetSourcei(mWindSource, AL_SOURCE_STATE, &playing);
if(playing != AL_PLAYING)
{
alSourcePlay(mWindSource);
LL_DEBUGS() << "Wind had stopped - probably ran out of buffers - restarting: " << (unprocessed+mNumEmptyWindALBuffers) << " now queued." << LL_ENDL;
}
}
+108
View File
@@ -0,0 +1,108 @@
/**
* @file audioengine_openal.cpp
* @brief implementation of audio engine using OpenAL
* support as a OpenAL 3D implementation
*
*
* $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$
*/
#ifndef LL_AUDIOENGINE_OPENAL_H
#define LL_AUDIOENGINE_OPENAL_H
#include "llaudioengine.h"
#include "lllistener_openal.h"
#include "llwindgen.h"
class LLAudioEngine_OpenAL : public LLAudioEngine
{
public:
LLAudioEngine_OpenAL();
virtual ~LLAudioEngine_OpenAL();
virtual bool init(void *user_data, const std::string &app_title);
virtual std::string getDriverName(bool verbose);
virtual LLStreamingAudioInterface* createDefaultStreamingAudioImpl() const { return nullptr; }
virtual void allocateListener();
virtual void shutdown();
void setInternalGain(F32 gain);
LLAudioBuffer* createBuffer();
LLAudioChannel* createChannel();
/*virtual*/ bool initWind();
/*virtual*/ void cleanupWind();
/*virtual*/ void updateWind(LLVector3 direction, F32 camera_altitude);
private:
typedef F32 WIND_SAMPLE_T;
LLWindGen<WIND_SAMPLE_T> *mWindGen;
F32 *mWindBuf;
U32 mWindBufFreq;
U32 mWindBufSamples;
U32 mWindBufBytes;
ALuint mWindSource;
int mNumEmptyWindALBuffers;
static const int MAX_NUM_WIND_BUFFERS = 80;
static const float WIND_BUFFER_SIZE_SEC; // 1/20th sec
};
class LLAudioChannelOpenAL : public LLAudioChannel
{
public:
LLAudioChannelOpenAL();
virtual ~LLAudioChannelOpenAL();
protected:
/*virtual*/ void play();
/*virtual*/ void playSynced(LLAudioChannel *channelp);
/*virtual*/ void cleanup();
/*virtual*/ bool isPlaying();
/*virtual*/ bool updateBuffer();
/*virtual*/ void update3DPosition();
/*virtual*/ void updateLoop();
ALuint mALSource;
ALint mLastSamplePos;
};
class LLAudioBufferOpenAL : public LLAudioBuffer{
public:
LLAudioBufferOpenAL();
virtual ~LLAudioBufferOpenAL();
bool loadWAV(const std::string& filename);
U32 getLength();
friend class LLAudioChannelOpenAL;
protected:
void cleanup();
ALuint getBuffer() {return mALBuffer;}
ALuint mALBuffer;
};
#endif
+136
View File
@@ -0,0 +1,136 @@
/**
* @file listener.cpp
* @brief Implementation of LISTENER class abstracting the audio support
*
* $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$
*/
#include "linden_common.h"
#include "lllistener.h"
#define DEFAULT_AT 0.0f,0.0f,-1.0f
#define DEFAULT_UP 0.0f,1.0f,0.0f
//-----------------------------------------------------------------------
// constructor
//-----------------------------------------------------------------------
LLListener::LLListener()
{
init();
}
//-----------------------------------------------------------------------
LLListener::~LLListener()
{
}
//-----------------------------------------------------------------------
void LLListener::init(void)
{
mPosition.zeroVec();
mListenAt.setVec(DEFAULT_AT);
mListenUp.setVec(DEFAULT_UP);
mVelocity.zeroVec();
}
//-----------------------------------------------------------------------
void LLListener::translate(LLVector3 offset)
{
mPosition += offset;
}
//-----------------------------------------------------------------------
void LLListener::setPosition(LLVector3 pos)
{
mPosition = pos;
}
//-----------------------------------------------------------------------
LLVector3 LLListener::getPosition(void)
{
return(mPosition);
}
//-----------------------------------------------------------------------
LLVector3 LLListener::getAt(void)
{
return(mListenAt);
}
//-----------------------------------------------------------------------
LLVector3 LLListener::getUp(void)
{
return(mListenUp);
}
//-----------------------------------------------------------------------
void LLListener::setVelocity(LLVector3 vel)
{
mVelocity = vel;
}
//-----------------------------------------------------------------------
void LLListener::orient(LLVector3 up, LLVector3 at)
{
mListenUp = up;
mListenAt = at;
}
//-----------------------------------------------------------------------
void LLListener::set(LLVector3 pos, LLVector3 vel, LLVector3 up, LLVector3 at)
{
mPosition = pos;
mVelocity = vel;
setPosition(pos);
setVelocity(vel);
orient(up,at);
}
//-----------------------------------------------------------------------
void LLListener::setDopplerFactor(F32 factor)
{
}
//-----------------------------------------------------------------------
F32 LLListener::getDopplerFactor()
{
return (1.f);
}
//-----------------------------------------------------------------------
void LLListener::setRolloffFactor(F32 factor)
{
}
//-----------------------------------------------------------------------
F32 LLListener::getRolloffFactor()
{
return (1.f);
}
//-----------------------------------------------------------------------
void LLListener::commitDeferredChanges()
{
}
+72
View File
@@ -0,0 +1,72 @@
/**
* @file listener.h
* @brief Description of LISTENER base class abstracting the audio support.
*
* $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$
*/
#ifndef LL_LISTENER_H
#define LL_LISTENER_H
#include "v3math.h"
class LLListener
{
private:
protected:
LLVector3 mPosition;
LLVector3 mVelocity;
LLVector3 mListenAt;
LLVector3 mListenUp;
public:
private:
protected:
public:
LLListener();
virtual ~LLListener();
virtual void init();
virtual void set(LLVector3 pos, LLVector3 vel, LLVector3 up, LLVector3 at);
virtual void setPosition(LLVector3 pos);
virtual void setVelocity(LLVector3 vel);
virtual void orient(LLVector3 up, LLVector3 at);
virtual void translate(LLVector3 offset);
virtual void setDopplerFactor(F32 factor);
virtual void setRolloffFactor(F32 factor);
virtual LLVector3 getPosition();
virtual LLVector3 getAt();
virtual LLVector3 getUp();
virtual F32 getDopplerFactor();
virtual F32 getRolloffFactor();
virtual void commitDeferredChanges();
};
#endif
+68
View File
@@ -0,0 +1,68 @@
/**
* @file listener_ds3d.h
* @brief Description of LISTENER class abstracting the audio support
* as a DirectSound 3D implementation (windows only)
*
* $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$
*/
#ifndef LL_LISTENER_DS3D_H
#define LL_LISTENER_DS3D_H
#include "lllistener.h"
#include <dmusici.h>
#include <dsound.h>
#include <ks.h>
class LLListener_DS3D : public LLListener
{
private:
protected:
IDirectSound3DListener8 *m3DListener;
public:
private:
protected:
public:
LLListener_DS3D();
virtual ~LLListener_DS3D();
virtual void init();
virtual void setDS3DLPtr (IDirectSound3DListener8 *listener_p);
virtual void translate(LLVector3 offset);
virtual void setPosition(LLVector3 pos);
virtual void setVelocity(LLVector3 vel);
virtual void orient(LLVector3 up, LLVector3 at);
virtual void setDopplerFactor(F32 factor);
virtual F32 getDopplerFactor();
virtual void setRolloffFactor(F32 factor);
virtual F32 getRolloffFactor();
virtual void commitDeferredChanges();
};
#endif
+136
View File
@@ -0,0 +1,136 @@
/**
* @file listener_fmodstudio.cpp
* @brief Implementation of LISTENER class abstracting the audio
* support as a FMODSTUDIO implementation
*
* $LicenseInfo:firstyear=2020&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2020, 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 "linden_common.h"
#include "llaudioengine.h"
#include "lllistener_fmodstudio.h"
#include "fmodstudio/fmod.hpp"
//-----------------------------------------------------------------------
// constructor
//-----------------------------------------------------------------------
LLListener_FMODSTUDIO::LLListener_FMODSTUDIO(FMOD::System *system)
{
mSystem = system;
init();
}
//-----------------------------------------------------------------------
LLListener_FMODSTUDIO::~LLListener_FMODSTUDIO()
{
}
//-----------------------------------------------------------------------
void LLListener_FMODSTUDIO::init(void)
{
// do inherited
LLListener::init();
mDopplerFactor = 1.0f;
mRolloffFactor = 1.0f;
}
//-----------------------------------------------------------------------
void LLListener_FMODSTUDIO::translate(LLVector3 offset)
{
LLListener::translate(offset);
mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, NULL, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV);
}
//-----------------------------------------------------------------------
void LLListener_FMODSTUDIO::setPosition(LLVector3 pos)
{
LLListener::setPosition(pos);
mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, NULL, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV);
}
//-----------------------------------------------------------------------
void LLListener_FMODSTUDIO::setVelocity(LLVector3 vel)
{
LLListener::setVelocity(vel);
mSystem->set3DListenerAttributes(0, NULL, (FMOD_VECTOR*)mVelocity.mV, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV);
}
//-----------------------------------------------------------------------
void LLListener_FMODSTUDIO::orient(LLVector3 up, LLVector3 at)
{
LLListener::orient(up, at);
// at = -at; by default Fmod studio is 'left-handed' but we are providing
// flag FMOD_INIT_3D_RIGHTHANDED so no correction are needed
mSystem->set3DListenerAttributes(0, NULL, NULL, (FMOD_VECTOR*)at.mV, (FMOD_VECTOR*)up.mV);
}
//-----------------------------------------------------------------------
void LLListener_FMODSTUDIO::commitDeferredChanges()
{
if (!mSystem)
{
return;
}
mSystem->update();
}
void LLListener_FMODSTUDIO::setRolloffFactor(F32 factor)
{
//An internal FMOD optimization skips 3D updates if there have not been changes to the 3D sound environment.
// (this was true for FMODex, looks to be still true for FMOD STUDIO, but needs a recheck)
//Sadly, a change in rolloff is not accounted for, thus we must touch the listener properties as well.
//In short: Changing the position ticks a dirtyflag inside fmod, which makes it not skip 3D processing next update call.
if (mRolloffFactor != factor)
{
LLVector3 pos = mPosition - LLVector3(0.f, 0.f, .1f);
mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)pos.mV, NULL, NULL, NULL);
mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, NULL, NULL, NULL);
}
mRolloffFactor = factor;
mSystem->set3DSettings(mDopplerFactor, 1.f, mRolloffFactor);
}
F32 LLListener_FMODSTUDIO::getRolloffFactor()
{
return mRolloffFactor;
}
void LLListener_FMODSTUDIO::setDopplerFactor(F32 factor)
{
mDopplerFactor = factor;
mSystem->set3DSettings(mDopplerFactor, 1.f, mRolloffFactor);
}
F32 LLListener_FMODSTUDIO::getDopplerFactor()
{
return mDopplerFactor;
}
+65
View File
@@ -0,0 +1,65 @@
/**
* @file listener_fmodstudio.h
* @brief Description of LISTENER class abstracting the audio support
* as an FMOD 3D implementation
*
* $LicenseInfo:firstyear=2020&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2020, 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$
*/
#ifndef LL_LISTENER_FMODSTUDIO_H
#define LL_LISTENER_FMODSTUDIO_H
#include "lllistener.h"
//Stubs
namespace FMOD
{
class System;
}
//Interfaces
class LLListener_FMODSTUDIO : public LLListener
{
public:
LLListener_FMODSTUDIO(FMOD::System *system);
virtual ~LLListener_FMODSTUDIO();
virtual void init();
virtual void translate(LLVector3 offset);
virtual void setPosition(LLVector3 pos);
virtual void setVelocity(LLVector3 vel);
virtual void orient(LLVector3 up, LLVector3 at);
virtual void commitDeferredChanges();
virtual void setDopplerFactor(F32 factor);
virtual F32 getDopplerFactor();
virtual void setRolloffFactor(F32 factor);
virtual F32 getRolloffFactor();
protected:
FMOD::System *mSystem;
F32 mDopplerFactor;
F32 mRolloffFactor;
};
#endif
+110
View File
@@ -0,0 +1,110 @@
/**
* @file audioengine_openal.cpp
* @brief implementation of audio engine using OpenAL
* support as a OpenAL 3D implementation
*
* $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 "linden_common.h"
#include "llaudioengine.h"
#include "lllistener_openal.h"
LLListener_OpenAL::LLListener_OpenAL()
{
init();
}
LLListener_OpenAL::~LLListener_OpenAL()
{
}
void LLListener_OpenAL::translate(LLVector3 offset)
{
//LL_INFOS() << "LLListener_OpenAL::translate() : " << offset << LL_ENDL;
LLListener::translate(offset);
}
void LLListener_OpenAL::setPosition(LLVector3 pos)
{
//LL_INFOS() << "LLListener_OpenAL::setPosition() : " << pos << LL_ENDL;
LLListener::setPosition(pos);
}
void LLListener_OpenAL::setVelocity(LLVector3 vel)
{
LLListener::setVelocity(vel);
}
void LLListener_OpenAL::orient(LLVector3 up, LLVector3 at)
{
//LL_INFOS() << "LLListener_OpenAL::orient() up: " << up << " at: " << at << LL_ENDL;
LLListener::orient(up, at);
}
void LLListener_OpenAL::commitDeferredChanges()
{
ALfloat orientation[6];
orientation[0] = mListenAt.mV[0];
orientation[1] = mListenAt.mV[1];
orientation[2] = mListenAt.mV[2];
orientation[3] = mListenUp.mV[0];
orientation[4] = mListenUp.mV[1];
orientation[5] = mListenUp.mV[2];
ALfloat velocity[3];
velocity[0] = mVelocity.mV[0];
velocity[1] = mVelocity.mV[1];
velocity[2] = mVelocity.mV[2];
alListenerfv(AL_ORIENTATION, orientation);
alListenerfv(AL_POSITION, mPosition.mV);
alListenerfv(AL_VELOCITY, velocity);
}
void LLListener_OpenAL::setDopplerFactor(F32 factor)
{
//LL_INFOS() << "LLListener_OpenAL::setDopplerFactor() : " << factor << LL_ENDL;
alDopplerFactor(factor);
}
F32 LLListener_OpenAL::getDopplerFactor()
{
ALfloat factor;
factor = alGetFloat(AL_DOPPLER_FACTOR);
//LL_INFOS() << "LLListener_OpenAL::getDopplerFactor() : " << factor << LL_ENDL;
return factor;
}
void LLListener_OpenAL::setRolloffFactor(F32 factor)
{
mRolloffFactor = factor;
}
F32 LLListener_OpenAL::getRolloffFactor()
{
return mRolloffFactor;
}
+59
View File
@@ -0,0 +1,59 @@
/**
* @file listener_openal.h
* @brief Description of LISTENER class abstracting the audio support
* as an OpenAL implementation
*
* $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$
*/
#ifndef LL_LISTENER_OPENAL_H
#define LL_LISTENER_OPENAL_H
#include "lllistener.h"
#include "AL/al.h"
#include "AL/alut.h"
#include "AL/alext.h"
class LLListener_OpenAL : public LLListener
{
public:
LLListener_OpenAL();
virtual ~LLListener_OpenAL();
virtual void translate(LLVector3 offset);
virtual void setPosition(LLVector3 pos);
virtual void setVelocity(LLVector3 vel);
virtual void orient(LLVector3 up, LLVector3 at);
virtual void commitDeferredChanges();
virtual void setDopplerFactor(F32 factor);
virtual F32 getDopplerFactor();
virtual void setRolloffFactor(F32 factor);
virtual F32 getRolloffFactor();
protected:
F32 mRolloffFactor;
};
#endif
+64
View File
@@ -0,0 +1,64 @@
/**
* @file streamingaudio.h
* @author Tofu Linden
* @brief Definition of LLStreamingAudioInterface base class abstracting the streaming audio interface
*
* $LicenseInfo:firstyear=2009&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$
*/
#ifndef LL_STREAMINGAUDIO_H
#define LL_STREAMINGAUDIO_H
#include "stdtypes.h" // from llcommon
#include <boost/signals2.hpp>
// Entirely abstract. Based exactly on the historic API.
class LLStreamingAudioInterface
{
public:
virtual ~LLStreamingAudioInterface() {}
virtual void start(const std::string& url) = 0;
virtual void stop() = 0;
virtual void pause(int pause) = 0;
virtual void update() = 0;
virtual int isPlaying() = 0;
// use a value from 0.0 to 1.0, inclusive
virtual void setGain(F32 vol) = 0;
virtual F32 getGain() = 0;
virtual std::string getURL() = 0;
virtual bool supportsAdjustableBufferSizes(){return false;}
virtual void setBufferSizes(U32 streambuffertime, U32 decodebuffertime){};
// These three are Firestorm additions and thus optional.
using metadata_update_callback_t = boost::signals2::signal<void(const LLSD& metadata)>;
virtual boost::signals2::connection setMetadataUpdateCallback(const metadata_update_callback_t::slot_type& cb) noexcept
{
return mMetadataUpdateSignal.connect(cb);
}
virtual LLSD getCurrentMetadata() const noexcept { return LLSD(); }
protected:
metadata_update_callback_t mMetadataUpdateSignal;
};
#endif // LL_STREAMINGAUDIO_H
@@ -0,0 +1,538 @@
/**
* @file streamingaudio_fmodstudio.cpp
* @brief LLStreamingAudio_FMODSTUDIO implementation
*
* $LicenseInfo:firstyear=2020&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2020, 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 "linden_common.h"
#include "llmath.h"
#include "fmodstudio/fmod.hpp"
#include "fmodstudio/fmod_errors.h"
#include "llstreamingaudio_fmodstudio.h"
inline bool Check_FMOD_Error(FMOD_RESULT result, const char *string)
{
if (result == FMOD_OK)
return false;
LL_WARNS("AudioImpl") << string << " Error: " << FMOD_ErrorString(result) << LL_ENDL;
return true;
}
class LLAudioStreamManagerFMODSTUDIO
{
public:
LLAudioStreamManagerFMODSTUDIO(FMOD::System *system, FMOD::ChannelGroup *group, const std::string& url);
FMOD::Channel* startStream();
bool stopStream(); // Returns true if the stream was successfully stopped.
bool ready();
const std::string& getURL() { return mInternetStreamURL; }
FMOD_RESULT getOpenState(FMOD_OPENSTATE& openstate, unsigned int* percentbuffered = NULL, bool* starving = NULL, bool* diskbusy = NULL);
protected:
FMOD::System* mSystem;
FMOD::ChannelGroup* mChannelGroup;
FMOD::Channel* mStreamChannel;
FMOD::Sound* mInternetStream;
bool mReady;
std::string mInternetStreamURL;
};
//---------------------------------------------------------------------------
// Internet Streaming
//---------------------------------------------------------------------------
LLStreamingAudio_FMODSTUDIO::LLStreamingAudio_FMODSTUDIO(FMOD::System *system) :
mSystem(system),
mCurrentInternetStreamp(NULL),
mStreamGroup(NULL),
mFMODInternetStreamChannelp(NULL),
mGain(1.0f),
mWasAlreadyPlaying(false)
{
// Number of milliseconds of audio to buffer for the audio card.
// Must be larger than the usual Second Life frame stutter time.
const U32 buffer_seconds = 10; //sec
const U32 estimated_bitrate = 128; //kbit/sec
Check_FMOD_Error(mSystem->setStreamBufferSize(estimated_bitrate * buffer_seconds * 128/*bytes/kbit*/, FMOD_TIMEUNIT_RAWBYTES), "FMOD::System::setStreamBufferSize");
Check_FMOD_Error(system->createChannelGroup("stream", &mStreamGroup), "FMOD::System::createChannelGroup");
}
LLStreamingAudio_FMODSTUDIO::~LLStreamingAudio_FMODSTUDIO()
{
stop();
for (U32 i = 0; i < 100; ++i)
{
if (releaseDeadStreams())
break;
ms_sleep(10);
}
}
void LLStreamingAudio_FMODSTUDIO::start(const std::string& url)
{
//if (!mInited)
//{
// LL_WARNS() << "startInternetStream before audio initialized" << LL_ENDL;
// return;
//}
// "stop" stream but don't clear url, etc. in case url == mInternetStreamURL
stop();
if (!url.empty())
{
if (mDeadStreams.empty())
{
LL_INFOS() << "Starting internet stream: " << url << LL_ENDL;
mCurrentInternetStreamp = new LLAudioStreamManagerFMODSTUDIO(mSystem, mStreamGroup, url);
mURL = url;
}
else
{
LL_INFOS() << "Deferring stream load until buffer release: " << url << LL_ENDL;
mPendingURL = url;
}
}
else
{
LL_INFOS() << "Set internet stream to null" << LL_ENDL;
mURL.clear();
}
}
void LLStreamingAudio_FMODSTUDIO::update()
{
if (!releaseDeadStreams())
{
llassert_always(mCurrentInternetStreamp == NULL);
return;
}
if (!mPendingURL.empty())
{
llassert_always(mCurrentInternetStreamp == NULL);
LL_INFOS() << "Starting internet stream: " << mPendingURL << LL_ENDL;
mCurrentInternetStreamp = new LLAudioStreamManagerFMODSTUDIO(mSystem, mStreamGroup, mPendingURL);
mURL = mPendingURL;
mPendingURL.clear();
}
// Don't do anything if there are no streams playing
if (!mCurrentInternetStreamp)
{
return;
}
unsigned int progress;
bool starving;
bool diskbusy;
FMOD_OPENSTATE open_state;
if (Check_FMOD_Error(mCurrentInternetStreamp->getOpenState(open_state, &progress, &starving, &diskbusy), "FMOD::Sound::getOpenState"))
{
LL_WARNS() << "Internet stream openstate error: open_state = " << open_state << " - progress = " << progress << " - starving = " << starving << " - diskbusy = " << diskbusy << LL_ENDL;
bool was_playing = mWasAlreadyPlaying;
stop();
// Try to restart previously playing stream on socket error
if (open_state == FMOD_OPENSTATE_ERROR && was_playing)
{
LL_WARNS() << "Stream was playing before - trying to restart" << LL_ENDL;
start(mURL);
}
return;
}
else if (open_state == FMOD_OPENSTATE_READY)
{
// Stream is live
// start the stream if it's ready
if (!mFMODInternetStreamChannelp &&
(mFMODInternetStreamChannelp = mCurrentInternetStreamp->startStream()))
{
// Reset volume to previously set volume
setGain(getGain());
Check_FMOD_Error(mFMODInternetStreamChannelp->setPaused(false), "FMOD::Channel::setPaused");
mWasAlreadyPlaying = true;
}
}
else if (open_state == FMOD_OPENSTATE_PLAYING)
{
if (!mWasAlreadyPlaying)
{
mWasAlreadyPlaying = true;
}
}
if (mFMODInternetStreamChannelp)
{
FMOD::Sound *sound = NULL;
if (!Check_FMOD_Error(mFMODInternetStreamChannelp->getCurrentSound(&sound), "FMOD::Channel::getCurrentSound") && sound)
{
FMOD_TAG tag;
S32 tagcount, numtagsupdated;
if (!Check_FMOD_Error(sound->getNumTags(&tagcount, &numtagsupdated), "FMOD::Sound::getNumTags") && numtagsupdated > 0)
{
LL_DEBUGS("StreamMetadata") << "Tag count: " << tagcount << " Tags updated since last call: " << numtagsupdated << LL_ENDL;
// <DKO> Stream metadata - originally by Shyotl Khur
mMetadata.clear();
// </DKO>
for (S32 i = 0; i < tagcount; ++i)
{
if (Check_FMOD_Error(sound->getTag(NULL, i, &tag), "FMOD::Sound::getTag"))
continue;
LL_DEBUGS("StreamMetadata") << "Tag name: " << tag.name << " - Tag type: " << tag.type << " - Tag data type: " << tag.datatype << LL_ENDL;
std::string name = tag.name;
switch (tag.type)
{
case FMOD_TAGTYPE_ID3V2:
{
if (name == "TIT2") name = "TITLE";
else if (name == "TPE1") name = "ARTIST";
break;
}
case FMOD_TAGTYPE_ASF:
{
if (name == "Title") name = "TITLE";
else if (name == "WM/AlbumArtist") name = "ARTIST";
break;
}
case FMOD_TAGTYPE_VORBISCOMMENT:
{
if (name == "title") name = "TITLE";
else if (name == "artist") name = "ARTIST";
break;
}
case FMOD_TAGTYPE_FMOD:
{
if (!strcmp(tag.name, "Sample Rate Change"))
{
LL_INFOS() << "Stream forced changing sample rate to " << *((float *)tag.data) << LL_ENDL;
mFMODInternetStreamChannelp->setFrequency(*((float *)tag.data));
}
continue;
}
default:
break;
}
switch (tag.datatype)
{
case FMOD_TAGDATATYPE_INT:
{
(mMetadata)[name]=*(LLSD::Integer*)(tag.data);
LL_DEBUGS("StreamMetadata") << tag.name << ": " << *(int*)(tag.data) << LL_ENDL;
break;
}
case FMOD_TAGDATATYPE_FLOAT:
{
(mMetadata)[name]=*(LLSD::Real*)(tag.data);
LL_DEBUGS("StreamMetadata") << tag.name << ": " << *(float*)(tag.data) << LL_ENDL;
break;
}
case FMOD_TAGDATATYPE_STRING:
{
std::string out = rawstr_to_utf8(std::string((char*)tag.data,tag.datalen));
(mMetadata)[name]=out;
LL_DEBUGS("StreamMetadata") << tag.name << ": " << out << LL_ENDL;
break;
}
case FMOD_TAGDATATYPE_STRING_UTF8:
{
std::string out((char*)tag.data);
(mMetadata)[name]=out;
LL_DEBUGS("StreamMetadata") << tag.name << ": " << out << LL_ENDL;
break;
}
case FMOD_TAGDATATYPE_STRING_UTF16:
{
std::string out((char*)tag.data,tag.datalen);
(mMetadata)[std::string(tag.name)]=out;
LL_DEBUGS("StreamMetadata") << tag.name << ": " << out << LL_ENDL;
break;
}
case FMOD_TAGDATATYPE_STRING_UTF16BE:
{
std::string out((char*)tag.data,tag.datalen);
//U16* buf = (U16*)out.c_str();
//for(U32 j = 0; j < out.size()/2; ++j)
//(((buf[j] & 0xff)<<8) | ((buf[j] & 0xff00)>>8));
(mMetadata)[std::string(tag.name)]=out;
LL_DEBUGS("StreamMetadata") << tag.name << ": " << out << LL_ENDL;
break;
}
default:
break;
}
}
mMetadataUpdateSignal(mMetadata);
}
if (starving)
{
bool paused = false;
if (!Check_FMOD_Error(mFMODInternetStreamChannelp->getPaused(&paused), "FMOD:Channel::getPaused") && !paused)
{
LL_INFOS() << "Stream starvation detected! Pausing stream until buffer nearly full." << LL_ENDL;
LL_INFOS() << " (diskbusy=" << diskbusy << ")" << LL_ENDL;
LL_INFOS() << " (progress=" << progress << ")" << LL_ENDL;
Check_FMOD_Error(mFMODInternetStreamChannelp->setPaused(true), "FMOD::Channel::setPaused");
}
}
else if (progress > 80)
{
Check_FMOD_Error(mFMODInternetStreamChannelp->setPaused(false), "FMOD::Channel::setPaused");
}
}
}
}
void LLStreamingAudio_FMODSTUDIO::stop()
{
mPendingURL.clear();
mWasAlreadyPlaying = false;
if (mFMODInternetStreamChannelp)
{
Check_FMOD_Error(mFMODInternetStreamChannelp->setPaused(true), "FMOD::Channel::setPaused");
Check_FMOD_Error(mFMODInternetStreamChannelp->setPriority(0), "FMOD::Channel::setPriority");
mFMODInternetStreamChannelp = NULL;
// <FS:Ansariel> Stream meta data display
mMetadata.clear();
mMetadataUpdateSignal(mMetadata);
// </FS:Ansariel>
}
if (mCurrentInternetStreamp)
{
LL_INFOS() << "Stopping internet stream: " << mCurrentInternetStreamp->getURL() << LL_ENDL;
if (mCurrentInternetStreamp->stopStream())
{
delete mCurrentInternetStreamp;
}
else
{
LL_WARNS() << "Pushing stream to dead list: " << mCurrentInternetStreamp->getURL() << LL_ENDL;
mDeadStreams.push_back(mCurrentInternetStreamp);
}
mCurrentInternetStreamp = NULL;
}
}
void LLStreamingAudio_FMODSTUDIO::pause(int pauseopt)
{
if (pauseopt < 0)
{
pauseopt = mCurrentInternetStreamp ? 1 : 0;
}
if (pauseopt)
{
if (mCurrentInternetStreamp)
{
stop();
}
}
else
{
start(getURL());
}
}
// A stream is "playing" if it has been requested to start. That
// doesn't necessarily mean audio is coming out of the speakers.
int LLStreamingAudio_FMODSTUDIO::isPlaying()
{
if (mCurrentInternetStreamp)
{
return 1; // Active and playing
}
else if (!mURL.empty() || !mPendingURL.empty())
{
return 2; // "Paused"
}
else
{
return 0;
}
}
F32 LLStreamingAudio_FMODSTUDIO::getGain()
{
return mGain;
}
std::string LLStreamingAudio_FMODSTUDIO::getURL()
{
return mURL;
}
void LLStreamingAudio_FMODSTUDIO::setGain(F32 vol)
{
mGain = vol;
if (mFMODInternetStreamChannelp)
{
vol = llclamp(vol * vol, 0.f, 1.f); //should vol be squared here?
Check_FMOD_Error(mFMODInternetStreamChannelp->setVolume(vol), "FMOD::Channel::setVolume");
}
}
///////////////////////////////////////////////////////
// manager of possibly-multiple internet audio streams
LLAudioStreamManagerFMODSTUDIO::LLAudioStreamManagerFMODSTUDIO(FMOD::System *system, FMOD::ChannelGroup *group, const std::string& url) :
mSystem(system),
mChannelGroup(group),
mStreamChannel(NULL),
mInternetStream(NULL),
mReady(false)
{
mInternetStreamURL = url;
FMOD_RESULT result = mSystem->createStream(url.c_str(), FMOD_2D | FMOD_NONBLOCKING | FMOD_IGNORETAGS, 0, &mInternetStream);
if (result != FMOD_OK)
{
LL_WARNS() << "Couldn't open fmod stream, error "
<< FMOD_ErrorString(result)
<< LL_ENDL;
mReady = false;
return;
}
mReady = true;
}
FMOD::Channel *LLAudioStreamManagerFMODSTUDIO::startStream()
{
// We need a live and opened stream before we try and play it.
FMOD_OPENSTATE open_state;
if (!mInternetStream || Check_FMOD_Error(getOpenState(open_state), "FMOD::Sound::getOpenState") || open_state != FMOD_OPENSTATE_READY)
{
LL_WARNS() << "No internet stream to start playing!" << LL_ENDL;
return NULL;
}
if (mStreamChannel)
return mStreamChannel; //Already have a channel for this stream.
Check_FMOD_Error(mSystem->playSound(mInternetStream, mChannelGroup, true, &mStreamChannel), "FMOD::System::playSound");
return mStreamChannel;
}
bool LLAudioStreamManagerFMODSTUDIO::stopStream()
{
if (mInternetStream)
{
bool close = true;
FMOD_OPENSTATE open_state;
if (!Check_FMOD_Error(getOpenState(open_state), "FMOD::Sound::getOpenState"))
{
switch (open_state)
{
case FMOD_OPENSTATE_CONNECTING:
close = false;
break;
default:
close = true;
}
}
if (close && !Check_FMOD_Error(mInternetStream->release(), "FMOD::Sound::release"))
{
mStreamChannel = NULL;
mInternetStream = NULL;
return true;
}
else
{
return false;
}
}
else
{
return true;
}
}
FMOD_RESULT LLAudioStreamManagerFMODSTUDIO::getOpenState(FMOD_OPENSTATE& state, unsigned int* percentbuffered, bool* starving, bool* diskbusy)
{
if (!mInternetStream)
return FMOD_ERR_INVALID_HANDLE;
FMOD_RESULT result = mInternetStream->getOpenState(&state, percentbuffered, starving, diskbusy);
Check_FMOD_Error(result, "FMOD::Sound::getOpenState");
return result;
}
void LLStreamingAudio_FMODSTUDIO::setBufferSizes(U32 streambuffertime, U32 decodebuffertime)
{
if (Check_FMOD_Error(mSystem->setStreamBufferSize(streambuffertime / 1000 * 128 * 128, FMOD_TIMEUNIT_RAWBYTES), "FMOD::System::setStreamBufferSize"))
return;
FMOD_ADVANCEDSETTINGS settings;
memset(&settings, 0, sizeof(settings));
settings.cbSize = sizeof(settings);
settings.defaultDecodeBufferSize = decodebuffertime;//ms
Check_FMOD_Error(mSystem->setAdvancedSettings(&settings), "FMOD::System::setAdvancedSettings");
}
bool LLStreamingAudio_FMODSTUDIO::releaseDeadStreams()
{
// Kill dead internet streams, if possible
std::list<LLAudioStreamManagerFMODSTUDIO *>::iterator iter;
for (iter = mDeadStreams.begin(); iter != mDeadStreams.end();)
{
LLAudioStreamManagerFMODSTUDIO *streamp = *iter;
if (streamp->stopStream())
{
LL_INFOS() << "Closed dead stream" << LL_ENDL;
delete streamp;
iter = mDeadStreams.erase(iter);
}
else
{
iter++;
}
}
return mDeadStreams.empty();
}
@@ -0,0 +1,85 @@
/**
* @file streamingaudio_fmodstudio.h
* @brief Definition of LLStreamingAudio_FMODSTUDIO implementation
*
* $LicenseInfo:firstyear=2020&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2020, 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$
*/
#ifndef LL_STREAMINGAUDIO_FMODSTUDIO_H
#define LL_STREAMINGAUDIO_FMODSTUDIO_H
#include "stdtypes.h" // from llcommon
#include "llstreamingaudio.h"
#include "lltimer.h"
//Stubs
class LLAudioStreamManagerFMODSTUDIO;
namespace FMOD
{
class System;
class Channel;
class ChannelGroup;
class DSP;
}
//Interfaces
class LLStreamingAudio_FMODSTUDIO : public LLStreamingAudioInterface
{
public:
LLStreamingAudio_FMODSTUDIO(FMOD::System *system);
/*virtual*/ ~LLStreamingAudio_FMODSTUDIO();
/*virtual*/ void start(const std::string& url);
/*virtual*/ void stop();
/*virtual*/ void pause(int pause);
/*virtual*/ void update();
/*virtual*/ int isPlaying();
/*virtual*/ void setGain(F32 vol);
/*virtual*/ F32 getGain();
/*virtual*/ std::string getURL();
/*virtual*/ bool supportsAdjustableBufferSizes(){return true;}
/*virtual*/ void setBufferSizes(U32 streambuffertime, U32 decodebuffertime);
//Streamtitle display DKO
LLSD getCurrentMetadata() const noexcept { return mMetadata; }
private:
bool releaseDeadStreams();
FMOD::System *mSystem;
LLAudioStreamManagerFMODSTUDIO *mCurrentInternetStreamp;
FMOD::ChannelGroup* mStreamGroup;
FMOD::Channel *mFMODInternetStreamChannelp;
std::list<LLAudioStreamManagerFMODSTUDIO *> mDeadStreams;
std::string mURL;
std::string mPendingURL;
F32 mGain;
// <DKO> Streamtitle display
LLSD mMetadata;
bool mWasAlreadyPlaying;
};
#endif // LL_STREAMINGAUDIO_FMODSTUDIO_H
+518
View File
@@ -0,0 +1,518 @@
/**
* @file vorbisencode.cpp
* @brief Vorbis encoding routine routine for Indra.
*
* $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$
*/
#include "linden_common.h"
#include "vorbis/vorbisenc.h"
#include "llvorbisencode.h"
#include "llerror.h"
#include "llrand.h"
#include "llmath.h"
#include "llapr.h"
//#if LL_DARWIN
// MBW -- XXX -- Getting rid of SecondLifeVorbis for now
#if 0
#include "VorbisFramework.h"
#define vorbis_analysis mac_vorbis_analysis
#define vorbis_analysis_headerout mac_vorbis_analysis_headerout
#define vorbis_analysis_init mac_vorbis_analysis_init
#define vorbis_encode_ctl mac_vorbis_encode_ctl
#define vorbis_encode_setup_init mac_vorbis_encode_setup_init
#define vorbis_encode_setup_managed mac_vorbis_encode_setup_managed
#define vorbis_info_init mac_vorbis_info_init
#define vorbis_info_clear mac_vorbis_info_clear
#define vorbis_comment_init mac_vorbis_comment_init
#define vorbis_comment_clear mac_vorbis_comment_clear
#define vorbis_block_init mac_vorbis_block_init
#define vorbis_block_clear mac_vorbis_block_clear
#define vorbis_dsp_clear mac_vorbis_dsp_clear
#define vorbis_analysis_buffer mac_vorbis_analysis_buffer
#define vorbis_analysis_wrote mac_vorbis_analysis_wrote
#define vorbis_analysis_blockout mac_vorbis_analysis_blockout
#define ogg_stream_packetin mac_ogg_stream_packetin
#define ogg_stream_init mac_ogg_stream_init
#define ogg_stream_flush mac_ogg_stream_flush
#define ogg_stream_pageout mac_ogg_stream_pageout
#define ogg_page_eos mac_ogg_page_eos
#define ogg_stream_clear mac_ogg_stream_clear
#endif
// <FS:Ansariel> FIRE-17812: Increase sounds length to 60s on OpenSim
//S32 check_for_invalid_wav_formats(const std::string& in_fname, std::string& error_msg)
S32 check_for_invalid_wav_formats(const std::string& in_fname, std::string& error_msg, bool is_in_secondlife)
// </FS:Ansariel>
{
U16 num_channels = 0;
U32 sample_rate = 0;
U32 bits_per_sample = 0;
U32 physical_file_size = 0;
U32 chunk_length = 0;
U32 raw_data_length = 0;
U32 bytes_per_sec = 0;
bool uncompressed_pcm = false;
unsigned char wav_header[44]; /*Flawfinder: ignore*/
error_msg.clear();
//********************************
LLAPRFile infile ;
infile.open(in_fname,LL_APR_RB);
//********************************
if (!infile.getFileHandle())
{
error_msg = "CannotUploadSoundFile";
return(LLVORBISENC_SOURCE_OPEN_ERR);
}
infile.read(wav_header, 44);
physical_file_size = infile.seek(APR_END,0);
if (strncmp((char *)&(wav_header[0]),"RIFF",4))
{
error_msg = "SoundFileNotRIFF";
return(LLVORBISENC_WAV_FORMAT_ERR);
}
if (strncmp((char *)&(wav_header[8]),"WAVE",4))
{
error_msg = "SoundFileNotRIFF";
return(LLVORBISENC_WAV_FORMAT_ERR);
}
// parse the chunks
U32 file_pos = 12; // start at the first chunk (usually fmt but not always)
while ((file_pos + 8)< physical_file_size)
{
infile.seek(APR_SET,file_pos);
infile.read(wav_header, 44);
chunk_length = ((U32) wav_header[7] << 24)
+ ((U32) wav_header[6] << 16)
+ ((U32) wav_header[5] << 8)
+ wav_header[4];
if (chunk_length > physical_file_size - file_pos - 4)
{
infile.close();
error_msg = "SoundFileInvalidChunkSize";
return(LLVORBISENC_CHUNK_SIZE_ERR);
}
// LL_INFOS() << "chunk found: '" << wav_header[0] << wav_header[1] << wav_header[2] << wav_header[3] << "'" << LL_ENDL;
if (!(strncmp((char *)&(wav_header[0]),"fmt ",4)))
{
if ((wav_header[8] == 0x01) && (wav_header[9] == 0x00))
{
uncompressed_pcm = true;
}
num_channels = ((U16) wav_header[11] << 8) + wav_header[10];
sample_rate = ((U32) wav_header[15] << 24)
+ ((U32) wav_header[14] << 16)
+ ((U32) wav_header[13] << 8)
+ wav_header[12];
bits_per_sample = ((U16) wav_header[23] << 8) + wav_header[22];
bytes_per_sec = ((U32) wav_header[19] << 24)
+ ((U32) wav_header[18] << 16)
+ ((U32) wav_header[17] << 8)
+ wav_header[16];
}
else if (!(strncmp((char *)&(wav_header[0]),"data",4)))
{
raw_data_length = chunk_length;
}
file_pos += (chunk_length + 8);
chunk_length = 0;
}
//****************
infile.close();
//****************
if (!uncompressed_pcm)
{
error_msg = "SoundFileNotPCM";
return(LLVORBISENC_PCM_FORMAT_ERR);
}
if ((num_channels < 1) || (num_channels > LLVORBIS_CLIP_MAX_CHANNELS))
{
error_msg = "SoundFileInvalidChannelCount";
return(LLVORBISENC_MULTICHANNEL_ERR);
}
if (sample_rate != LLVORBIS_CLIP_SAMPLE_RATE)
{
error_msg = "SoundFileInvalidSampleRate";
return(LLVORBISENC_UNSUPPORTED_SAMPLE_RATE);
}
if ((bits_per_sample != 16) && (bits_per_sample != 8))
{
error_msg = "SoundFileInvalidWordSize";
return(LLVORBISENC_UNSUPPORTED_WORD_SIZE);
}
if (!raw_data_length)
{
error_msg = "SoundFileInvalidHeader";
return(LLVORBISENC_CLIP_TOO_LONG);
}
F32 clip_length = (F32)raw_data_length/(F32)bytes_per_sec;
// <FS:Ansariel> FIRE-17812: Increase sounds length to 60s on OpenSim
//if (clip_length > LLVORBIS_CLIP_MAX_TIME)
if (clip_length > (is_in_secondlife ? LLVORBIS_CLIP_MAX_TIME : LLVORBIS_CLIP_MAX_TIME_OPENSIM))
// </FS:Ansariel>
{
error_msg = "SoundFileInvalidTooLong";
return(LLVORBISENC_CLIP_TOO_LONG);
}
return(LLVORBISENC_NOERR);
}
// <FS:Ansariel> FIRE-17812: Increase sounds length to 60s on OpenSim
//S32 encode_vorbis_file(const std::string& in_fname, const std::string& out_fname)
S32 encode_vorbis_file(const std::string& in_fname, const std::string& out_fname, bool is_in_secondlife)
// </FS:Ansariel>
{
#define READ_BUFFER 1024
unsigned char readbuffer[READ_BUFFER*4+44]; /* out of the data segment, not the stack */ /*Flawfinder: ignore*/
ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */
ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */
ogg_packet op; /* one raw packet of data for decode */
vorbis_info vi; /* struct that stores all the static vorbis bitstream settings */
vorbis_comment vc; /* struct that stores all the user comments */
vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
vorbis_block vb; /* local working space for packet->PCM decode */
int eos=0;
int result;
U16 num_channels = 0;
U32 sample_rate = 0;
U32 bits_per_sample = 0;
S32 format_error = 0;
std::string error_msg;
// <FS:Ansariel> FIRE-17812: Increase sounds length to 60s on OpenSim
//if ((format_error = check_for_invalid_wav_formats(in_fname, error_msg)))
if ((format_error = check_for_invalid_wav_formats(in_fname, error_msg, is_in_secondlife)))
// </FS:Ansariel>
{
LL_WARNS() << error_msg << ": " << in_fname << LL_ENDL;
return(format_error);
}
#if 1
unsigned char wav_header[44]; /*Flawfinder: ignore*/
S32 data_left = 0;
LLAPRFile infile ;
infile.open(in_fname,LL_APR_RB);
if (!infile.getFileHandle())
{
LL_WARNS() << "Couldn't open temporary ogg file for writing: " << in_fname
<< LL_ENDL;
return(LLVORBISENC_SOURCE_OPEN_ERR);
}
LLAPRFile outfile ;
outfile.open(out_fname,LL_APR_WPB);
if (!outfile.getFileHandle())
{
LL_WARNS() << "Couldn't open upload sound file for reading: " << in_fname
<< LL_ENDL;
return(LLVORBISENC_DEST_OPEN_ERR);
}
// parse the chunks
U32 chunk_length = 0;
U32 file_pos = 12; // start at the first chunk (usually fmt but not always)
while (infile.eof() != APR_EOF)
{
infile.seek(APR_SET,file_pos);
infile.read(wav_header, 44);
chunk_length = ((U32) wav_header[7] << 24)
+ ((U32) wav_header[6] << 16)
+ ((U32) wav_header[5] << 8)
+ wav_header[4];
// LL_INFOS() << "chunk found: '" << wav_header[0] << wav_header[1] << wav_header[2] << wav_header[3] << "'" << LL_ENDL;
if (!(strncmp((char *)&(wav_header[0]),"fmt ",4)))
{
num_channels = ((U16) wav_header[11] << 8) + wav_header[10];
sample_rate = ((U32) wav_header[15] << 24)
+ ((U32) wav_header[14] << 16)
+ ((U32) wav_header[13] << 8)
+ wav_header[12];
bits_per_sample = ((U16) wav_header[23] << 8) + wav_header[22];
}
else if (!(strncmp((char *)&(wav_header[0]),"data",4)))
{
infile.seek(APR_SET,file_pos+8);
// leave the file pointer at the beginning of the data chunk data
data_left = chunk_length;
break;
}
file_pos += (chunk_length + 8);
chunk_length = 0;
}
/********** Encode setup ************/
/* choose an encoding mode */
/* (mode 0: 44kHz stereo uncoupled, roughly 128kbps VBR) */
vorbis_info_init(&vi);
// always encode to mono
// SL-52913 & SL-53779 determined this quality level to be our 'good
// enough' general-purpose quality level with a nice low bitrate.
// Equivalent to oggenc -q0.5
F32 quality = 0.05f;
// quality = (bitrate==128000 ? 0.4f : 0.1);
// if (vorbis_encode_init(&vi, /* num_channels */ 1 ,sample_rate, -1, bitrate, -1))
if (vorbis_encode_init_vbr(&vi, /* num_channels */ 1 ,sample_rate, quality))
// if (vorbis_encode_setup_managed(&vi,1,sample_rate,-1,bitrate,-1) ||
// vorbis_encode_ctl(&vi,OV_ECTL_RATEMANAGE_AVG,NULL) ||
// vorbis_encode_setup_init(&vi))
{
LL_WARNS() << "unable to initialize vorbis codec at quality " << quality << LL_ENDL;
// LL_WARNS() << "unable to initialize vorbis codec at bitrate " << bitrate << LL_ENDL;
return(LLVORBISENC_DEST_OPEN_ERR);
}
/* add a comment */
vorbis_comment_init(&vc);
// vorbis_comment_add(&vc,"Linden");
/* set up the analysis state and auxiliary encoding storage */
vorbis_analysis_init(&vd,&vi);
vorbis_block_init(&vd,&vb);
/* set up our packet->stream encoder */
/* pick a random serial number; that way we can more likely build
chained streams just by concatenation */
ogg_stream_init(&os, ll_rand());
/* Vorbis streams begin with three headers; the initial header (with
most of the codec setup parameters) which is mandated by the Ogg
bitstream spec. The second header holds any comment fields. The
third header holds the bitstream codebook. We merely need to
make the headers, then pass them to libvorbis one at a time;
libvorbis handles the additional Ogg bitstream constraints */
{
ogg_packet header;
ogg_packet header_comm;
ogg_packet header_code;
vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code);
ogg_stream_packetin(&os,&header); /* automatically placed in its own
page */
ogg_stream_packetin(&os,&header_comm);
ogg_stream_packetin(&os,&header_code);
/* We don't have to write out here, but doing so makes streaming
* much easier, so we do, flushing ALL pages. This ensures the actual
* audio data will start on a new page
*/
while(!eos){
int result=ogg_stream_flush(&os,&og);
if(result==0)break;
outfile.write(og.header, og.header_len);
outfile.write(og.body, og.body_len);
}
}
while(!eos)
{
long bytes_per_sample = bits_per_sample/8;
long bytes=(long)infile.read(readbuffer,llclamp((S32)(READ_BUFFER*num_channels*bytes_per_sample),0,data_left)); /* stereo hardwired here */
if (bytes==0)
{
/* end of file. this can be done implicitly in the mainline,
but it's easier to see here in non-clever fashion.
Tell the library we're at end of stream so that it can handle
the last frame and mark end of stream in the output properly */
vorbis_analysis_wrote(&vd,0);
// eos = 1;
}
else
{
long i;
long samples;
int temp;
data_left -= bytes;
/* data to encode */
/* expose the buffer to submit data */
float **buffer=vorbis_analysis_buffer(&vd,READ_BUFFER);
i = 0;
samples = bytes / (num_channels * bytes_per_sample);
if (num_channels == 2)
{
if (bytes_per_sample == 2)
{
/* uninterleave samples */
for(i=0; i<samples ;i++)
{
temp = ((signed char *)readbuffer)[i*4+1]; /*Flawfinder: ignore*/
temp += ((signed char *)readbuffer)[i*4+3]; /*Flawfinder: ignore*/
temp <<= 8;
temp += readbuffer[i*4];
temp += readbuffer[i*4+2];
buffer[0][i] = ((float)temp) / 65536.f;
}
}
else // presume it's 1 byte per which is unsigned (F#@%ing wav "standard")
{
/* uninterleave samples */
for(i=0; i<samples ;i++)
{
temp = readbuffer[i*2+0];
temp += readbuffer[i*2+1];
temp -= 256;
buffer[0][i] = ((float)temp) / 256.f;
}
}
}
else if (num_channels == 1)
{
if (bytes_per_sample == 2)
{
for(i=0; i < samples ;i++)
{
temp = ((signed char*)readbuffer)[i*2+1];
temp <<= 8;
temp += readbuffer[i*2];
buffer[0][i] = ((float)temp) / 32768.f;
}
}
else // presume it's 1 byte per which is unsigned (F#@%ing wav "standard")
{
for(i=0; i < samples ;i++)
{
temp = readbuffer[i];
temp -= 128;
buffer[0][i] = ((float)temp) / 128.f;
}
}
}
/* tell the library how much we actually submitted */
vorbis_analysis_wrote(&vd,i);
}
/* vorbis does some data preanalysis, then divvies up blocks for
more involved (potentially parallel) processing. Get a single
block for encoding now */
while(vorbis_analysis_blockout(&vd,&vb)==1)
{
/* analysis */
/* Do the main analysis, creating a packet */
vorbis_analysis(&vb, NULL);
vorbis_bitrate_addblock(&vb);
while(vorbis_bitrate_flushpacket(&vd, &op))
{
/* weld the packet into the bitstream */
ogg_stream_packetin(&os,&op);
/* write out pages (if any) */
while(!eos)
{
result = ogg_stream_pageout(&os,&og);
if(result==0)
break;
outfile.write(og.header, og.header_len);
outfile.write(og.body, og.body_len);
/* this could be set above, but for illustrative purposes, I do
it here (to show that vorbis does know where the stream ends) */
if(ogg_page_eos(&og))
eos=1;
}
}
}
}
/* clean up and exit. vorbis_info_clear() must be called last */
ogg_stream_clear(&os);
vorbis_block_clear(&vb);
vorbis_dsp_clear(&vd);
vorbis_comment_clear(&vc);
vorbis_info_clear(&vi);
/* ogg_page and ogg_packet structs always point to storage in
libvorbis. They're never freed or manipulated directly */
// fprintf(stderr,"Vorbis encoding: Done.\n");
LL_INFOS() << "Vorbis encoding: Done." << LL_ENDL;
#endif
return(LLVORBISENC_NOERR);
}
+64
View File
@@ -0,0 +1,64 @@
/**
* @file vorbisencode.h
* @brief Vorbis encoding routine routine for Indra.
*
* $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$
*/
#ifndef LL_VORBISENCODE_H
#define LL_VORBISENCODE_H
const S32 LLVORBISENC_NOERR = 0; // no error
const S32 LLVORBISENC_SOURCE_OPEN_ERR = 1; // error opening source
const S32 LLVORBISENC_DEST_OPEN_ERR = 2; // error opening destination
const S32 LLVORBISENC_WAV_FORMAT_ERR = 3; // not a WAV
const S32 LLVORBISENC_PCM_FORMAT_ERR = 4; // not a PCM
const S32 LLVORBISENC_MONO_ERR = 5; // can't do mono
const S32 LLVORBISENC_STEREO_ERR = 6; // can't do stereo
const S32 LLVORBISENC_MULTICHANNEL_ERR = 7; // can't do stereo
const S32 LLVORBISENC_UNSUPPORTED_SAMPLE_RATE = 8; // unsupported sample rate
const S32 LLVORBISENC_UNSUPPORTED_WORD_SIZE = 9; // unsupported word size
const S32 LLVORBISENC_CLIP_TOO_LONG = 10; // source file is too long
const S32 LLVORBISENC_CHUNK_SIZE_ERR = 11; // chunk size is wrong
const F32 LLVORBIS_CLIP_MAX_TIME = 30.0f;
const F32 LLVORBIS_CLIP_MAX_TIME_OPENSIM = 60.0f; // <FS:Ansariel> FIRE-17812: Increase sounds length to 60s on OpenSim
const U8 LLVORBIS_CLIP_MAX_CHANNELS = 2;
const U32 LLVORBIS_CLIP_SAMPLE_RATE = 44100;
const U32 LLVORBIS_CLIP_MAX_SAMPLES_PER_CHANNEL = (U32)(LLVORBIS_CLIP_MAX_TIME * LLVORBIS_CLIP_SAMPLE_RATE);
const U32 LLVORBIS_CLIP_MAX_SAMPLES = LLVORBIS_CLIP_MAX_SAMPLES_PER_CHANNEL * LLVORBIS_CLIP_MAX_CHANNELS;
const size_t LLVORBIS_CLIP_MAX_SAMPLE_DATA = LLVORBIS_CLIP_MAX_SAMPLES * 2; // 2 = 16-bit
// Treat anything this long as a bad asset. A little fudge factor at the end:
// Make that a lot of fudge factor. We're allowing 30 sec for now - 3x legal upload
const size_t LLVORBIS_CLIP_REJECT_SAMPLES = LLVORBIS_CLIP_MAX_SAMPLES * 3;
const size_t LLVORBIS_CLIP_REJECT_SIZE = LLVORBIS_CLIP_MAX_SAMPLE_DATA * 3;
// <FS:Ansariel> FIRE-17812: Increase sounds length to 60s on OpenSim
//S32 check_for_invalid_wav_formats(const std::string& in_fname, std::string& error_msg);
//S32 encode_vorbis_file(const std::string& in_fname, const std::string& out_fname);
S32 check_for_invalid_wav_formats(const std::string& in_fname, std::string& error_msg, bool is_in_secondlife);
S32 encode_vorbis_file(const std::string& in_fname, const std::string& out_fname, bool is_in_secondlife);
// </FS:Ansariel>
#endif
+174
View File
@@ -0,0 +1,174 @@
/**
* @file windgen.h
* @brief Templated wind noise generation
*
* $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$
*/
#ifndef WINDGEN_H
#define WINDGEN_H
#include "llcommon.h"
#include "llrand.h"
template <class MIXBUFFERFORMAT_T>
class LLWindGen
{
public:
LLWindGen(const U32 sample_rate = 44100) :
mTargetGain(0.f),
mTargetFreq(100.f),
mTargetPanGainR(0.5f),
mInputSamplingRate(sample_rate),
mSubSamples(2),
mFilterBandWidth(50.f),
mBuf0(0.0f),
mBuf1(0.0f),
mBuf2(0.0f),
mY0(0.0f),
mY1(0.0f),
mCurrentGain(0.f),
mCurrentFreq(100.f),
mCurrentPanGainR(0.5f),
mLastSample(0.f)
{
mSamplePeriod = (F32)mSubSamples / (F32)mInputSamplingRate;
mB2 = expf(-F_TWO_PI * mFilterBandWidth * mSamplePeriod);
}
const U32 getInputSamplingRate() { return mInputSamplingRate; }
const F32 getNextSample();
const F32 getClampedSample(bool clamp, F32 sample);
// newbuffer = the buffer passed from the previous DSP unit.
// numsamples = length in samples-per-channel at this mix time.
// NOTE: generates L/R interleaved stereo
MIXBUFFERFORMAT_T* windGenerate(MIXBUFFERFORMAT_T *newbuffer, int numsamples)
{
MIXBUFFERFORMAT_T *cursamplep = newbuffer;
// Filter coefficients
F32 a0 = 0.0f, b1 = 0.0f;
// No need to clip at normal volumes
bool clip = mCurrentGain > 2.0f;
bool interp_freq = false;
//if the frequency isn't changing much, we don't need to interpolate in the inner loop
if (llabs(mTargetFreq - mCurrentFreq) < (mCurrentFreq * 0.112))
{
// calculate resonant filter coefficients
mCurrentFreq = mTargetFreq;
b1 = (-4.0f * mB2) / (1.0f + mB2) * cosf(F_TWO_PI * (mCurrentFreq * mSamplePeriod));
a0 = (1.0f - mB2) * sqrtf(1.0f - (b1 * b1) / (4.0f * mB2));
}
else
{
interp_freq = true;
}
while (numsamples)
{
F32 next_sample;
// Start with white noise
// This expression is fragile, rearrange it and it will break!
next_sample = getNextSample();
// Apply a pinking filter
// Magic numbers taken from PKE method at http://www.firstpr.com.au/dsp/pink-noise/
mBuf0 = mBuf0 * 0.99765f + next_sample * 0.0990460f;
mBuf1 = mBuf1 * 0.96300f + next_sample * 0.2965164f;
mBuf2 = mBuf2 * 0.57000f + next_sample * 1.0526913f;
next_sample = mBuf0 + mBuf1 + mBuf2 + next_sample * 0.1848f;
if (interp_freq)
{
// calculate and interpolate resonant filter coefficients
mCurrentFreq = (0.999f * mCurrentFreq) + (0.001f * mTargetFreq);
b1 = (-4.0f * mB2) / (1.0f + mB2) * cosf(F_TWO_PI * (mCurrentFreq * mSamplePeriod));
a0 = (1.0f - mB2) * sqrtf(1.0f - (b1 * b1) / (4.0f * mB2));
}
// Apply a resonant low-pass filter on the pink noise
next_sample = a0 * next_sample - b1 * mY0 - mB2 * mY1;
mY1 = mY0;
mY0 = next_sample;
mCurrentGain = (0.999f * mCurrentGain) + (0.001f * mTargetGain);
mCurrentPanGainR = (0.999f * mCurrentPanGainR) + (0.001f * mTargetPanGainR);
// For a 3dB pan law use:
// next_sample *= mCurrentGain * ((mCurrentPanGainR*(mCurrentPanGainR-1)*1.652+1.413);
next_sample *= mCurrentGain;
// delta is used to interpolate between synthesized samples
F32 delta = (next_sample - mLastSample) / (F32)mSubSamples;
// Fill the audio buffer, clipping if necessary
for (U8 i=mSubSamples; i && numsamples; --i, --numsamples)
{
mLastSample = mLastSample + delta;
MIXBUFFERFORMAT_T sample_right = (MIXBUFFERFORMAT_T)getClampedSample(clip, mLastSample * mCurrentPanGainR);
MIXBUFFERFORMAT_T sample_left = (MIXBUFFERFORMAT_T)getClampedSample(clip, mLastSample - (F32)sample_right);
*cursamplep = sample_left;
++cursamplep;
*cursamplep = sample_right;
++cursamplep;
}
}
return newbuffer;
}
public:
F32 mTargetGain;
F32 mTargetFreq;
F32 mTargetPanGainR;
private:
U32 mInputSamplingRate;
U8 mSubSamples;
F32 mSamplePeriod;
F32 mFilterBandWidth;
F32 mB2;
F32 mBuf0;
F32 mBuf1;
F32 mBuf2;
F32 mY0;
F32 mY1;
F32 mCurrentGain;
F32 mCurrentFreq;
F32 mCurrentPanGainR;
F32 mLastSample;
};
template<class T> inline const F32 LLWindGen<T>::getNextSample() { return (F32)rand() * (1.0f / (F32)(RAND_MAX / (U16_MAX / 8))) + (F32)(S16_MIN / 8); }
template<> inline const F32 LLWindGen<F32>::getNextSample() { return ll_frand()-.5f; }
template<class T> inline const F32 LLWindGen<T>::getClampedSample(bool clamp, F32 sample) { return clamp ? (F32)llclamp((S32)sample,(S32)S16_MIN,(S32)S16_MAX) : sample; }
template<> inline const F32 LLWindGen<F32>::getClampedSample(bool clamp, F32 sample) { return sample; }
#endif