475 lines
12 KiB
C++
475 lines
12 KiB
C++
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program 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 Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include "stdsound.h"
|
|
|
|
#include "nel/sound/sample_bank.h"
|
|
#include "nel/sound/sample_bank_manager.h"
|
|
#include "nel/sound/driver/sound_driver.h"
|
|
#include "nel/sound/driver/buffer.h"
|
|
#include "nel/misc/path.h"
|
|
#include "nel/misc/file.h"
|
|
#include "nel/sound/async_file_manager_sound.h"
|
|
#include "nel/sound/background_sound_manager.h"
|
|
#include "nel/sound/sound_bank.h"
|
|
|
|
|
|
using namespace std;
|
|
using namespace NLMISC;
|
|
|
|
|
|
namespace NLSOUND {
|
|
|
|
/// Constante for the number of file to load asynchronously at a time.
|
|
uint32 ASYNC_LOADING_SPLIT = 10; // 10 file by 10 file
|
|
|
|
|
|
// ********************************************************
|
|
|
|
CSampleBank::CSampleBank(NLMISC::TStringId name, CSampleBankManager *sampleBankManager)
|
|
: _SampleBankManager(sampleBankManager), _Name(name), _Loaded(false), _LoadingDone(true), _ByteSize(0)
|
|
{
|
|
_SampleBankManager->m_Banks.insert(make_pair(name, this));
|
|
}
|
|
|
|
|
|
// ********************************************************
|
|
|
|
CSampleBank::~CSampleBank()
|
|
{
|
|
_SampleBankManager->m_AudioMixer->unregisterUpdate(this);
|
|
while (!_LoadingDone)
|
|
{
|
|
// need to wait for loading end.
|
|
nlSleep(100);
|
|
}
|
|
|
|
if (_Loaded)
|
|
unload();
|
|
|
|
// remove the bank from the list of known banks
|
|
for (CSampleBankManager::TSampleBankContainer::iterator
|
|
it(_SampleBankManager->m_Banks.begin()),
|
|
end(_SampleBankManager->m_Banks.end()); it != end; ++it)
|
|
{
|
|
if (it->second == this)
|
|
{
|
|
_SampleBankManager->m_Banks.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// delete all the samples.
|
|
while (!_Samples.empty())
|
|
{
|
|
delete _Samples.begin()->second;
|
|
_Samples.erase(_Samples.begin());
|
|
}
|
|
|
|
_Samples.clear();
|
|
}
|
|
|
|
|
|
// ********************************************************
|
|
|
|
void CSampleBank::load(bool async)
|
|
{
|
|
// TODO : add async loading support !
|
|
|
|
CSampleBankManager::TVirtualBankCont::iterator it(_SampleBankManager->m_VirtualBanks.find(_Name));
|
|
if (it != _SampleBankManager->m_VirtualBanks.end())
|
|
{
|
|
// this is a virtual sample bank !
|
|
nlinfo("Loading virtual sample bank %s", CStringMapper::unmap(_Name).c_str());
|
|
|
|
const CAudioMixerUser::TBackgroundFlags &flags = _SampleBankManager->m_AudioMixer->getBackgroundFlags();
|
|
|
|
for (uint i=0; i<it->second.size(); ++i)
|
|
{
|
|
if (flags.Flags[it->second[i].Filter])
|
|
{
|
|
CSampleBank *bank = _SampleBankManager->findSampleBank(it->second[i].BankName);
|
|
if (bank)
|
|
bank->load(async);
|
|
}
|
|
}
|
|
}
|
|
|
|
//nlinfo("Loading sample bank %s %", CStringMapper::unmap(_Name).c_str(), async?"":"Asynchronously");
|
|
|
|
vector<string> filenames;
|
|
// vector<string>::iterator iter;
|
|
|
|
if (_Loaded)
|
|
{
|
|
nlwarning("Trying to load an already loaded bank : %s", CStringMapper::unmap(_Name).c_str ());
|
|
return;
|
|
}
|
|
|
|
|
|
// Load the sample bank from the builded sample_bank file.
|
|
string bankName(CStringMapper::unmap(_Name)+".sample_bank");
|
|
string filename = CPath::lookup(bankName, false);
|
|
if (filename.empty())
|
|
{
|
|
nlwarning("Could not find sample bank [%s]", bankName.c_str());
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
|
|
CIFile sampleBank(filename);
|
|
|
|
CAudioMixerUser::TSampleBankHeader sbh;
|
|
sampleBank.serial(sbh);
|
|
_LoadingDone = false;
|
|
|
|
sint32 seekStart = sampleBank.getPos();
|
|
|
|
|
|
uint8 *data = 0;
|
|
uint i;
|
|
for (i=0; i<sbh.Name.size(); ++i)
|
|
{
|
|
IBuffer *ibuffer = _SampleBankManager->m_AudioMixer->getSoundDriver()->createBuffer();
|
|
nlassert(ibuffer);
|
|
|
|
TStringId nameId = CStringMapper::map(CFile::getFilenameWithoutExtension(sbh.Name[i]));
|
|
ibuffer->setName(nameId);
|
|
|
|
/* {
|
|
sint16 *data16 = new sint16[sbh.NbSample[i]];
|
|
IBuffer::TADPCMState state;
|
|
state.PreviousSample = 0;
|
|
state.StepIndex = 0;
|
|
uint count =0;
|
|
for (count=0; count+1024<sbh.NbSample[i]; count+=1024)
|
|
{
|
|
IBuffer::decodeADPCM(data+count/2, data16+count, 1024, state);
|
|
}
|
|
IBuffer::decodeADPCM(data+count/2, data16+count, sbh.NbSample[i]-count, state);
|
|
|
|
state.PreviousSample = 0;
|
|
state.StepIndex = 0;
|
|
sint16 *data16_2 = new sint16[sbh.NbSample[i]];
|
|
IBuffer::decodeADPCM(data, data16_2, sbh.NbSample[i], state);
|
|
|
|
for (uint j=0; j<sbh.NbSample[i]; ++j)
|
|
{
|
|
if (data16[j] != data16_2[j])
|
|
{
|
|
nlwarning("Sample differ at %u", j);
|
|
}
|
|
}
|
|
|
|
_SoundDriver->readRawBuffer(ibuffer, sbh.Name[i], (uint8*)data16, sbh.NbSample[i]*2, Mono16, sbh.Freq[i]);
|
|
delete [] data16;
|
|
delete [] data16_2;
|
|
}
|
|
*/
|
|
|
|
if (_SampleBankManager->m_AudioMixer->useAPDCM())
|
|
{
|
|
data = (uint8*) realloc(data, sbh.SizeAdpcm[i]);
|
|
sampleBank.seek(seekStart + sbh.OffsetAdpcm[i], CIFile::begin);
|
|
sampleBank.serialBuffer(data, sbh.SizeAdpcm[i]);
|
|
ibuffer->setFormat(IBuffer::FormatDviAdpcm, 1, 16, sbh.Freq[i]);
|
|
if (!ibuffer->fill(data, sbh.SizeAdpcm[i]))
|
|
nlwarning("AM: ibuffer->fill returned false with FormatADPCM");
|
|
}
|
|
else
|
|
{
|
|
data = (uint8*) realloc(data, sbh.SizeMono16[i]);
|
|
sampleBank.seek(seekStart + sbh.OffsetMono16[i], CIFile::begin);
|
|
sampleBank.serialBuffer(data, sbh.SizeMono16[i]);
|
|
ibuffer->setFormat(IBuffer::FormatPcm, 1, 16, sbh.Freq[i]);
|
|
if (!ibuffer->fill(data, sbh.SizeMono16[i]))
|
|
nlwarning("AM: ibuffer->fill returned false with FormatPCM");
|
|
}
|
|
|
|
_ByteSize += ibuffer->getSize();
|
|
|
|
_Samples[nameId] = ibuffer;
|
|
|
|
// Warn the sound bank that the sample are available.
|
|
CAudioMixerUser::getInstance()->getSoundBank()->bufferLoaded(nameId, ibuffer);
|
|
}
|
|
free(data);
|
|
|
|
_SampleBankManager->m_LoadedSize += _ByteSize;
|
|
}
|
|
catch(const Exception &e)
|
|
{
|
|
// loading failed !
|
|
nlwarning("Exception %s during loading of sample bank %s", e.what(), filename.c_str());
|
|
|
|
if (_SampleBankManager->m_AudioMixer->getPackedSheetUpdate())
|
|
{
|
|
nlinfo("Deleting offending sound bank, you need to restart to recreate it!");
|
|
CFile::deleteFile(filename);
|
|
}
|
|
}
|
|
|
|
_Loaded = true;
|
|
_LoadingDone = true;
|
|
|
|
|
|
|
|
///////////////////////////////////////// OLD Version //////////////////////////////////////
|
|
/*
|
|
|
|
std::string list = CPath::lookup(CStringMapper::unmap(_Name)+CAudioMixerUser::SampleBankListExt, false);
|
|
if (list.empty())
|
|
{
|
|
nlwarning("File %s not found to load sample bank %s", (CStringMapper::unmap(_Name)+CAudioMixerUser::SampleBankListExt).c_str(), CStringMapper::unmap(_Name).c_str());
|
|
return;
|
|
}
|
|
|
|
|
|
NLMISC::CIFile sampleBankList(list);
|
|
sampleBankList.serialCont(filenames);
|
|
|
|
for (iter = filenames.begin(); iter != filenames.end(); iter++)
|
|
{
|
|
IBuffer* ibuffer = NULL;
|
|
try
|
|
{
|
|
ibuffer = _SoundDriver->createBuffer();
|
|
nlassert(ibuffer);
|
|
|
|
// std::string sampleName(CFile::getFilenameWithoutExtension(*iter));
|
|
NLMISC::TStringId sampleName(CStringMapper::map(CFile::getFilenameWithoutExtension(*iter)));
|
|
|
|
if (async)
|
|
{
|
|
ibuffer->presetName(sampleName);
|
|
nldebug("Preloading sample [%s]", CStringMapper::unmap(sampleName).c_str());
|
|
}
|
|
else
|
|
{
|
|
std::string fullName = NLMISC::CPath::lookup(*iter, false);
|
|
if (!fullName.empty())
|
|
{
|
|
NLMISC::CIFile ifile(fullName);
|
|
uint size = ifile.getFileSize();
|
|
uint8 *buffer = new uint8[ifile.getFileSize()];
|
|
ifile.serialBuffer(buffer, size);
|
|
|
|
_SoundDriver->readWavBuffer(ibuffer, fullName, buffer, size);
|
|
_ByteSize += ibuffer->getSize();
|
|
|
|
delete [] buffer;
|
|
}
|
|
}
|
|
_Samples[sampleName] = ibuffer ;
|
|
|
|
// Warn the sound bank that the sample are available.
|
|
CSoundBank::instance()->bufferLoaded(sampleName, ibuffer);
|
|
}
|
|
catch (const ESoundDriver &e)
|
|
{
|
|
if (ibuffer != NULL) {
|
|
delete ibuffer;
|
|
ibuffer = NULL;
|
|
}
|
|
nlwarning("Problem with file '%s': %s", (*iter).c_str(), e.what());
|
|
}
|
|
}
|
|
|
|
_Loaded = true;
|
|
|
|
if (!async)
|
|
{
|
|
_LoadingDone = true;
|
|
// compute the sample bank size.
|
|
_LoadedSize += _ByteSize;
|
|
}
|
|
else
|
|
{
|
|
// fill the loading list.
|
|
TSampleTable::iterator first(_Samples.begin()), last(_Samples.end());
|
|
for (; first != last; ++first)
|
|
{
|
|
_LoadList.push_back(make_pair(first->second, first->first));
|
|
}
|
|
_SplitLoadDone = false;
|
|
// send the first files
|
|
for (uint i=0; i<ASYNC_LOADING_SPLIT && !_LoadList.empty(); ++i)
|
|
{
|
|
CAsyncFileManagerSound::getInstance().loadWavFile(_LoadList.front().first, CStringMapper::unmap(_LoadList.front().second)+".wav");
|
|
_LoadList.pop_front();
|
|
}
|
|
// add a end loading event...
|
|
CAsyncFileManagerSound::getInstance().signal(&_SplitLoadDone);
|
|
// and register for update on the mixer
|
|
CAudioMixerUser::instance()->registerUpdate(this);
|
|
}
|
|
*/
|
|
}
|
|
|
|
void CSampleBank::onUpdate()
|
|
{
|
|
if (_SplitLoadDone)
|
|
{
|
|
nldebug("Some samples have been loaded");
|
|
if (_LoadList.empty())
|
|
{
|
|
// all the samples are loaded, we can compute the bank size.
|
|
TSampleTable::iterator first(_Samples.begin()), last(_Samples.end());
|
|
for (; first != last; ++first)
|
|
{
|
|
_ByteSize += first->second->getSize();
|
|
}
|
|
|
|
_SampleBankManager->m_LoadedSize += _ByteSize;
|
|
|
|
// stop the update.
|
|
_SampleBankManager->m_AudioMixer->unregisterUpdate(this);
|
|
_LoadingDone = true;
|
|
|
|
// Force an update in the background manager (can restar stopped sound).
|
|
_SampleBankManager->m_AudioMixer->getBackgroundSoundManager()->updateBackgroundStatus();
|
|
|
|
nlinfo("Sample bank %s loaded.", CStringMapper::unmap(_Name).c_str());
|
|
}
|
|
else
|
|
{
|
|
_SplitLoadDone = false;
|
|
for (uint i=0; i<ASYNC_LOADING_SPLIT && !_LoadList.empty(); ++i)
|
|
{
|
|
CAsyncFileManagerSound::getInstance().loadWavFile(_LoadList.front().first, CStringMapper::unmap(_LoadList.front().second)+".wav");
|
|
_LoadList.pop_front();
|
|
}
|
|
// add a end loading event...
|
|
CAsyncFileManagerSound::getInstance().signal(&_SplitLoadDone);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ********************************************************
|
|
|
|
bool CSampleBank::unload()
|
|
{
|
|
vector<IBuffer*> vec;
|
|
TSampleTable::iterator it;
|
|
|
|
if (!_Loaded)
|
|
{
|
|
nlwarning("Trying to unload an already unloaded bank : %s", CStringMapper::unmap(_Name).c_str ());
|
|
return true;
|
|
}
|
|
|
|
// need to wait end of load ?
|
|
if (!_LoadingDone)
|
|
return false;
|
|
|
|
//nlinfo("Unloading sample bank %s", CStringMapper::unmap(_Name).c_str());
|
|
|
|
for (it = _Samples.begin(); it != _Samples.end(); ++it)
|
|
{
|
|
IBuffer *buffer = it->second;
|
|
if (buffer)
|
|
{
|
|
const NLMISC::TStringId & bufferName = buffer->getName();
|
|
|
|
CAudioMixerUser *audioMixer = _SampleBankManager->m_AudioMixer;
|
|
// Warn the mixer to stop any track playing this buffer.
|
|
audioMixer->bufferUnloaded(buffer);
|
|
|
|
// Warn the sound banks about this buffer.
|
|
audioMixer->getSoundBank()->bufferUnloaded(bufferName);
|
|
|
|
// delete
|
|
it->second = NULL;
|
|
delete buffer;
|
|
}
|
|
}
|
|
|
|
_Loaded = false;
|
|
|
|
_SampleBankManager->m_LoadedSize -= _ByteSize;
|
|
_ByteSize = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
// ********************************************************
|
|
|
|
bool CSampleBank::isLoaded()
|
|
{
|
|
return _Loaded;
|
|
}
|
|
|
|
// ********************************************************
|
|
|
|
IBuffer* CSampleBank::getSample(const NLMISC::TStringId &name)
|
|
{
|
|
{
|
|
/* // dump the sample list.
|
|
TSampleTable::iterator it (_Samples.begin()), last(_Samples.end());
|
|
std::string s;
|
|
|
|
// while (first != last)
|
|
for (it = _Samples.begin(); it != _Samples.end(); ++it)
|
|
{
|
|
s += std::string(" [")+it->first+"] ";
|
|
//first++;
|
|
}
|
|
|
|
nldebug("getSample(%s) : sample list = [%s]", name, s.c_str());
|
|
*/
|
|
}
|
|
|
|
// Find sound
|
|
TSampleTable::iterator iter = _Samples.find(name);
|
|
if ( iter == _Samples.end() )
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return (*iter).second;
|
|
}
|
|
}
|
|
|
|
// ********************************************************
|
|
|
|
uint CSampleBank::countSamples()
|
|
{
|
|
return (uint)_Samples.size();
|
|
}
|
|
|
|
// ********************************************************
|
|
|
|
uint CSampleBank::getSize()
|
|
{
|
|
uint size = 0;
|
|
|
|
TSampleTable::const_iterator iter;
|
|
for (iter = _Samples.begin(); iter != _Samples.end(); iter++)
|
|
{
|
|
size += (*iter).second->getSize();
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
|
|
|
|
} // namespace NLSOUND
|
|
|