// NeL - MMORPG Framework // 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 . #include "stdsound.h" #include "nel/misc/hierarchical_timer.h" #include "nel/misc/progress_callback.h" #include "nel/misc/big_file.h" #include "nel/misc/time_nl.h" #include "nel/misc/command.h" #include "nel/misc/file.h" #include "nel/misc/path.h" #include "nel/georges/u_form_loader.h" #include "nel/georges/u_form_elm.h" #include "nel/georges/load_form.h" #include "nel/georges/u_form.h" #include "nel/3d/scene_user.h" #include "nel/sound/driver/sound_driver.h" #include "nel/sound/driver/buffer.h" #include "nel/sound/driver/effect.h" #include "nel/sound/background_sound_manager.h" #include "nel/sound/background_sound_manager.h" #include "nel/sound/music_sound_manager.h" #include "nel/sound/background_source.h" #include "nel/sound/clustered_sound.h" #include "nel/sound/complex_source.h" #include "nel/sound/simple_source.h" #include "nel/sound/complex_sound.h" #include "nel/sound/context_sound.h" #include "nel/sound/music_source.h" #include "nel/sound/stream_source.h" #include "nel/sound/simple_sound.h" #include "nel/sound/music_sound.h" #include "nel/sound/stream_sound.h" #include "nel/sound/sample_bank_manager.h" #include "nel/sound/sample_bank.h" #include "nel/sound/sound_bank.h" using namespace std; using namespace NLMISC; namespace NLSOUND { #define NL_TRACE_MIXER 0 #if NL_TRACE_MIXER #define _profile(_a) nldebug ## _a #else #define _profile(_a) #endif // Return the priority cstring (debug info) const char *PriToCStr [NbSoundPriorities] = { "XH", "HI", "MD", "LO" }; // ****************************************************************** const char *getPriorityStr( TSoundPriority p ) { nlassert( ((uint)p) < NbSoundPriorities ); return PriToCStr[p]; } // ****************************************************************** UAudioMixer *UAudioMixer::createAudioMixer() { return new CAudioMixerUser(); } // ****************************************************************** CAudioMixerUser::CAudioMixerUser() : _AutoLoadSample(false), _UseADPCM(true), _SoundDriver(NULL), _SoundBank(NULL), _SampleBankManager(NULL), _BackgroundSoundManager(NULL), _ClusteredSound(0), _ReverbEffect(NULL), _ListenPosition(CVector::Null), _BackgroundMusicManager(NULL), _PlayingSources(0), _PlayingSourcesMuted(0), _Leaving(false) { #if NL_PROFILE_MIXER _UpdateTime = 0.0; _CreateTime = 0.0; _UpdateCount = 0; _CreateCount = 0; #endif // init the filter names and short names for (uint i=0; i(uscene)->getScene()); initClusteredSound(scene, minGain, maxDistance, portalInterpolate); } void CAudioMixerUser::initClusteredSound(NL3D::CScene *scene, float minGain, float maxDistance, float portalInterpolate = 20.0f) { if (!_ClusteredSound) _ClusteredSound = new CClusteredSound(); _ClusteredSound->init(scene, portalInterpolate, maxDistance, minGain); } void CAudioMixerUser::setPriorityReserve(TSoundPriority priorityChannel, uint reserve) { _PriorityReserve[priorityChannel] = (uint32)min((uint)_Tracks.size(), reserve); } void CAudioMixerUser::setLowWaterMark(uint value) { _LowWaterMark = (uint32)min((uint)_Tracks.size(), value); } // ****************************************************************** void CAudioMixerUser::writeProfile(std::string& out) { // compute number of muted source /* uint nb = 0; TSourceContainer::iterator first(_Sources.begin()), last(_Sources.end()); for (; first != last; ++first) { CSimpleSource *psu = *first; if (psu->getTrack() == NULL) { ++nb; } } hash_set::const_iterator ips; for ( ips=_Sources.begin(); ips!=_Sources.end(); ++ips ) { CSimpleSource *psu = *ips; if (psu->getTrack() == NULL) { ++nb; } } */ out += "Sound mixer: \n"; out += "\tPlaying sources: " + toString (getPlayingSourcesCount()) + " \n"; out += "\tAvailable tracks: " + toString (getAvailableTracksCount()) + " \n"; out += "\tUsed tracks: " + toString (getUsedTracksCount()) + " \n"; // out << "\tMuted sources: " << nb << " \n"; // out << "\tMuted sources: " << max(0, sint(_PlayingSources.size())-sint(_NbTracks)) << " \n"; // out << "\tMuted sources: " << max(0, sint(_PlayingSources)-sint(_NbTracks)) << " \n"; out += "\tMuted sources: " + toString ((int)_PlayingSourcesMuted) + "\n"; out += "\tSources waiting for play: " + toString (_SourceWaitingForPlay.size()) + " \n"; out += "\tHighestPri: " + toString ((int)_ReserveUsage[HighestPri]) + " / " + toString ((int)_PriorityReserve[HighestPri]) + " \n"; out += "\tHighPri: " + toString ((int)_ReserveUsage[HighPri]) + " / " + toString ((int)_PriorityReserve[HighPri]) + "\n"; out += "\tMidPri: " + toString ((int)_ReserveUsage[MidPri]) + " / " + toString ((int)_PriorityReserve[MidPri]) + " \n"; out += "\tLowPri: " + toString ((int)_ReserveUsage[LowPri]) + " / " + toString ((int)_PriorityReserve[LowPri]) + " \n"; out += "\tFreeTracks: " + toString (_FreeTracks.size()) + " / " + toString (_Tracks.size()) + " \n"; out += "\tAverage update time: " + toString (1000.0 * _UpdateTime / iavoid0(_UpdateCount)) + " msec\n"; out += "\tAverage create time: " + toString (1000.0 * _CreateTime / iavoid0(_CreateCount)) + " msec\n"; out += "\tEstimated CPU: " + toString ((100.0 * 1000.0 * (_UpdateTime + _CreateTime) / curTime())) + "%%\n"; if (_SoundDriver) { out += toString ("Sound driver: \n"); _SoundDriver->writeProfile(out); } } // ****************************************************************** void CAudioMixerUser::addSourceWaitingForPlay(CSourceCommon *source) { _SourceWaitingForPlay.push_back(source); } // ****************************************************************** void CAudioMixerUser::reset() { _Leaving = true; _SourceWaitingForPlay.clear(); /* TODO: Stop music channels */ // Stop tracks uint i; for (i = 0; i < _Tracks.size(); ++i) { if (_Tracks[i]) { CSourceCommon* src = _Tracks[i]->getLogicalSource(); if (src && src->isPlaying()) { src->stop(); } } } // Do a first multipass travesal to stop all playing source // We can't do the work in 1 pass because stoping a source can lead to // destruction of sub source, invalidating the iterators ! bool again; do { again = false; TSourceContainer::iterator first(_Sources.begin()), last(_Sources.end()); for (; first != last; ++first) { if ((*first)->isPlaying()) { (*first)->stop(); again = true; break; } } } while (again); // Sources while (!_Sources.empty()) { //removeSource( _Sources.begin(), true ); // 3D sources, the envsounds were removed above CSourceCommon *source = *(_Sources.begin()); if (source->isPlaying()) source->stop(); else delete source; } _Leaving = false; } void CAudioMixerUser::setPackedSheetOption(const std::string &path, bool update) { _PackedSheetPath = CPath::standardizePath(path, true); _UpdatePackedSheet = update; } void CAudioMixerUser::setSamplePath(const std::string& path) { _SampleWavPath = CPath::standardizePath(path, true); _SampleBankPath = _SampleBankPath; } void CAudioMixerUser::setSamplePaths(const std::string &wavAssetPath, const std::string &bankBuildPath) { _SampleWavPath = wavAssetPath; _SampleBankPath = bankBuildPath; } // ****************************************************************** void CAudioMixerUser::init(uint maxTrack, bool useEax, bool useADPCM, IProgressCallback *progressCallBack, bool autoLoadSample, TDriver driverType, bool forceSoftwareBuffer, bool manualRolloff) { nlctassert(NumDrivers == UAudioMixer::TDriver(ISoundDriver::NumDrivers)); initDriver(NLSOUND::ISoundDriver::getDriverName((ISoundDriver::TDriver)driverType)); CInitInfo initInfo; initInfo.MaxTrack = maxTrack; initInfo.EnableReverb = useEax; // :) initInfo.EnableOccludeObstruct = useEax; // :) initInfo.UseADPCM = useADPCM; initInfo.ForceSoftware = forceSoftwareBuffer; initInfo.ManualRolloff = manualRolloff; initInfo.AutoLoadSample = autoLoadSample; initDevice("", initInfo, progressCallBack); } /// Initialize the NeL Sound Driver with given driverName. void CAudioMixerUser::initDriver(const std::string &driverName) { std::string dn = NLMISC::toLower(driverName); nldebug("AM: Init Driver '%s' ('%s')...", driverName.c_str(), dn.c_str()); ISoundDriver::TDriver driverType; if (dn == "auto") driverType = ISoundDriver::DriverAuto; else if (dn == "fmod") driverType = ISoundDriver::DriverFMod; else if (dn == "dsound") driverType = ISoundDriver::DriverDSound; else if (dn == "openal") driverType = ISoundDriver::DriverOpenAl; else if (dn == "xaudio2") driverType = ISoundDriver::DriverXAudio2; else { driverType = ISoundDriver::DriverAuto; nlwarning("AM: driverName value '%s' ('%s') is invalid.", driverName.c_str(), dn.c_str()); } try { // create the wanted driver nlctassert(NumDrivers == UAudioMixer::TDriver(ISoundDriver::NumDrivers)); _SoundDriver = ISoundDriver::createDriver(this, driverType); nlassert(_SoundDriver); } catch (const ESoundDriver &e) { nlwarning(e.what()); delete _SoundDriver; _SoundDriver = NULL; throw; } catch (...) { delete _SoundDriver; _SoundDriver = NULL; throw; } } /// Get the available devices on the loaded driver. void CAudioMixerUser::getDevices(std::vector &devices) { if (!_SoundDriver) { nlwarning("AM: You must call 'initDriver' before calling 'getDevices'"); return; } _SoundDriver->getDevices(devices); } /// Initialize the selected device on the currently initialized driver. Leave deviceName empty to select the default device. void CAudioMixerUser::initDevice(const std::string &deviceName, const CInitInfo &initInfo, NLMISC::IProgressCallback *progressCallback) { if (!_SoundDriver) { nlwarning("AM: You must call 'initDriver' before calling 'initDevice'"); return; } nldebug( "AM: Init Device..." ); _profile(( "AM: ---------------------------------------------------------------" )); _UseEax = initInfo.EnableReverb || initInfo.EnableOccludeObstruct; // [TODO KAETEMI: Fixme.] _UseADPCM = initInfo.UseADPCM; _AutoLoadSample = initInfo.AutoLoadSample; bool manualRolloff = initInfo.ManualRolloff; bool forceSoftware = initInfo.ForceSoftware; uint maxTrack = initInfo.MaxTrack; // Init sound driver try { _profile(( "AM: DRIVER: %s", _SoundDriver->getDllName().c_str() )); // the options to init the driver sint driverOptions = ISoundDriver::OptionHasBufferStreaming; if (_UseEax) driverOptions |= ISoundDriver::OptionEnvironmentEffects; if (_UseADPCM) driverOptions |= ISoundDriver::OptionAllowADPCM; if (forceSoftware) driverOptions |= ISoundDriver::OptionSoftwareBuffer; if (manualRolloff) driverOptions |= ISoundDriver::OptionManualRolloff; if (_AutoLoadSample) driverOptions |= ISoundDriver::OptionLocalBufferCopy; // init the driver with selected device and needed options _SoundDriver->initDevice(deviceName, (ISoundDriver::TSoundOptions)driverOptions); // verify the options, OptionHasBufferStreaming not checked if (_UseEax && !_SoundDriver->getOption(ISoundDriver::OptionEnvironmentEffects)) { nlwarning("AM: OptionEnvironmentEffects not available, _UseEax = false"); _UseEax = false; } if (_UseADPCM && !_SoundDriver->getOption(ISoundDriver::OptionAllowADPCM)) { nlwarning("AM: OptionAllowADPCM not available, _UseADPCM = false"); _UseADPCM = false; } if (_AutoLoadSample && !_SoundDriver->getOption(ISoundDriver::OptionLocalBufferCopy)) { nlwarning("AM: OptionLocalBufferCopy not available, _AutoLoadSample = false"); _AutoLoadSample = false; } if (forceSoftware && !_SoundDriver->getOption(ISoundDriver::OptionSoftwareBuffer)) { nlwarning("AM: OptionSoftwareBuffer not available, forceSoftwareBuffer = false"); forceSoftware = false; // not really needed, but set anyway in case this is still used later in this function } if (manualRolloff && !_SoundDriver->getOption(ISoundDriver::OptionLocalBufferCopy)) { nlwarning("AM: OptionManualRolloff not available, manualRolloff = false"); manualRolloff = false; // not really needed, but set anyway in case this is still used later in this function } } catch (const ESoundDriver &e) { nlwarning(e.what()); delete _SoundDriver; _SoundDriver = NULL; throw; } catch (...) { delete _SoundDriver; _SoundDriver = NULL; throw; } uint i; // Init registrable classes static bool initialized = false; if (!initialized) { initialized = true; } // Init listener _Listener.init(_SoundDriver); // Init environment reverb effects if (_UseEax) { _ReverbEffect = _SoundDriver->createReverbEffect(); if (!_ReverbEffect) { _UseEax = false; } else // createEffect succeeded, add environments { nldebug("AM: Reverb OK"); // todo: loading this data from a file or something would be neat // also: check if this should go into clustered_sound (background_sound_manager also uses this stuff at one point, though) // effect presets (based on I3DL2 specification/guidelines, see 3dl2help.h) addEnvironment("GENERIC", IReverbEffect::CEnvironment( -10.00f, -1.00f, 1.49f,0.83f, -26.02f,0.007f, 2.00f,0.011f,100.0f,100.0f)); addEnvironment("PADDEDCELL", IReverbEffect::CEnvironment( -10.00f,-60.00f, 0.17f,0.10f, -12.04f,0.001f, 2.07f,0.002f,100.0f,100.0f)); addEnvironment("ROOM", IReverbEffect::CEnvironment( -10.00f, -4.54f, 0.40f,0.83f, -16.46f,0.002f, 0.53f,0.003f,100.0f,100.0f)); addEnvironment("BATHROOM", IReverbEffect::CEnvironment( -10.00f,-12.00f, 1.49f,0.54f, -3.70f,0.007f, 10.30f,0.011f,100.0f, 60.0f)); addEnvironment("LIVINGROOM", IReverbEffect::CEnvironment( -10.00f,-60.00f, 0.50f,0.10f, -13.76f,0.003f, -11.04f,0.004f,100.0f,100.0f)); addEnvironment("STONEROOM", IReverbEffect::CEnvironment( -10.00f, -3.00f, 2.31f,0.64f, -7.11f,0.012f, 0.83f,0.017f,100.0f,100.0f)); addEnvironment("AUDITORIUM", IReverbEffect::CEnvironment( -10.00f, -4.76f, 4.32f,0.59f, -7.89f,0.020f, -2.89f,0.030f,100.0f,100.0f)); addEnvironment("CONCERTHALL", IReverbEffect::CEnvironment( -10.00f, -5.00f, 3.92f,0.70f, -12.30f,0.020f, -0.02f,0.029f,100.0f,100.0f)); addEnvironment("CAVE", IReverbEffect::CEnvironment( -10.00f, 0.00f, 2.91f,1.30f, -6.02f,0.015f, -3.02f,0.022f,100.0f,100.0f)); addEnvironment("ARENA", IReverbEffect::CEnvironment( -10.00f, -6.98f, 7.24f,0.33f, -11.66f,0.020f, 0.16f,0.030f,100.0f,100.0f)); addEnvironment("HANGAR", IReverbEffect::CEnvironment( -10.00f,-10.00f,10.05f,0.23f, -6.02f,0.020f, 1.98f,0.030f,100.0f,100.0f)); addEnvironment("CARPETEDHALLWAY", IReverbEffect::CEnvironment( -10.00f,-40.00f, 0.30f,0.10f, -18.31f,0.002f, -16.30f,0.030f,100.0f,100.0f)); addEnvironment("HALLWAY", IReverbEffect::CEnvironment( -10.00f, -3.00f, 1.49f,0.59f, -12.19f,0.007f, 4.41f,0.011f,100.0f,100.0f)); addEnvironment("STONECORRIDOR", IReverbEffect::CEnvironment( -10.00f, -2.37f, 2.70f,0.79f, -12.14f,0.013f, 3.95f,0.020f,100.0f,100.0f)); addEnvironment("ALLEY", IReverbEffect::CEnvironment( -10.00f, -2.70f, 1.49f,0.86f, -12.04f,0.007f, -0.04f,0.011f,100.0f,100.0f)); addEnvironment("FOREST", IReverbEffect::CEnvironment( -10.00f,-33.00f, 1.49f,0.54f, -25.60f,0.162f, -6.13f,0.088f, 79.0f,100.0f)); addEnvironment("CITY", IReverbEffect::CEnvironment( -10.00f, -8.00f, 1.49f,0.67f, -22.73f,0.007f, -22.17f,0.011f, 50.0f,100.0f)); addEnvironment("MOUNTAINS", IReverbEffect::CEnvironment( -10.00f,-25.00f, 1.49f,0.21f, -27.80f,0.300f, -20.14f,0.100f, 27.0f,100.0f)); addEnvironment("QUARRY", IReverbEffect::CEnvironment( -10.00f,-10.00f, 1.49f,0.83f,-100.00f,0.061f, 5.00f,0.025f,100.0f,100.0f)); addEnvironment("PLAIN", IReverbEffect::CEnvironment( -10.00f,-20.00f, 1.49f,0.50f, -24.66f,0.179f, -25.14f,0.100f, 21.0f,100.0f)); addEnvironment("PARKINGLOT", IReverbEffect::CEnvironment( -10.00f, 0.00f, 1.65f,1.50f, -13.63f,0.008f, -11.53f,0.012f,100.0f,100.0f)); addEnvironment("SEWERPIPE", IReverbEffect::CEnvironment( -10.00f,-10.00f, 2.81f,0.14f, 4.29f,0.014f, 6.48f,0.021f, 80.0f, 60.0f)); addEnvironment("UNDERWATER", IReverbEffect::CEnvironment( -10.00f,-40.00f, 1.49f,0.10f, -4.49f,0.007f, 17.00f,0.011f,100.0f,100.0f)); addEnvironment("SMALLROOM", IReverbEffect::CEnvironment( -10.00f, -6.00f, 1.10f,0.83f, -4.00f,0.005f, 5.00f,0.010f,100.0f,100.0f)); addEnvironment("MEDIUMROOM", IReverbEffect::CEnvironment( -10.00f, -6.00f, 1.30f,0.83f, -10.00f,0.010f, -2.00f,0.020f,100.0f,100.0f)); addEnvironment("LARGEROOM", IReverbEffect::CEnvironment( -10.00f, -6.00f, 1.50f,0.83f, -16.00f,0.020f, -10.00f,0.040f,100.0f,100.0f)); addEnvironment("MEDIUMHALL", IReverbEffect::CEnvironment( -10.00f, -6.00f, 1.80f,0.70f, -13.00f,0.015f, -8.00f,0.030f,100.0f,100.0f)); addEnvironment("LARGEHALL", IReverbEffect::CEnvironment( -10.00f, -6.00f, 1.80f,0.70f, -20.00f,0.030f, -14.00f,0.060f,100.0f,100.0f)); addEnvironment("PLATE", IReverbEffect::CEnvironment( -10.00f, -2.00f, 1.30f,0.90f, 0.00f,0.002f, 0.00f,0.010f,100.0f, 75.0f)); // these are the default environment settings in case no environment data is available (you'll hear this one) _DefaultEnvironment = getEnvironment("PLAIN"); _DefaultRoomSize = 7.5f; // note: 'no fx' generally does not use the default room size _Environments[CStringMapper::map("no fx")] = _DefaultEnvironment; // set the default environment now _ReverbEffect->setEnvironment(_DefaultEnvironment, _DefaultRoomSize); } } // Init tracks (physical sources) changeMaxTrack(maxTrack); // Init the reserve stuff. _LowWaterMark = 0; for (i=0; iload(getPackedSheetPath(), getPackedSheetUpdate()); nlinfo("AM: Initialized audio mixer with %u voices, %s and %s.", (uint32)_Tracks.size(), _UseEax ? "with EAX support" : "WITHOUT EAX", _UseADPCM ? "with ADPCM sample source" : "with 16 bits PCM sample source"); // Init the sample bank manager CSampleBankManager *sampleBankManager = new CSampleBankManager(this); _SampleBankManager = sampleBankManager; // try to load default configuration from george sheet NLGEORGES::UFormLoader *formLoader = NULL; try { std::string mixerConfigFile = NLMISC::CPath::lookup("default.mixer_config", false); if (!mixerConfigFile.empty()) { formLoader = NLGEORGES::UFormLoader::createLoader(); NLMISC::CSmartPtr form; form = formLoader->loadForm(mixerConfigFile.c_str()); NLGEORGES::UFormElm &root = form->getRootNode(); // read track reserve uint32 highestRes, highRes, midRes, lowRes; root.getValueByName(highestRes, ".HighestPriorityReserve"); root.getValueByName(highRes, ".HighPriorityReserve"); root.getValueByName(midRes, ".MidPriorityReserve"); root.getValueByName(lowRes, ".LowPriorityReserve"); setPriorityReserve(HighestPri, highestRes); setPriorityReserve(HighPri, highRes); setPriorityReserve(MidPri, midRes); setPriorityReserve(LowPri, lowRes); uint32 lowWater; root.getValueByName(lowWater, ".LowWaterMark"); setLowWaterMark(lowWater); // preload sample bank NLGEORGES::UFormElm *sampleBanks; root.getNodeByName(&sampleBanks, ".SampleBanks"); if (sampleBanks != NULL) { uint size; sampleBanks->getArraySize(size); for (uint i=0; igetArrayValue(name, i); if (!name.empty()) loadSampleBank(false, name); if (progressCallback != 0) progressCallback->progress(float(i) / size); } } // configure background flags names, fades and state NLGEORGES::UFormElm *bgFlags; root.getNodeByName(&bgFlags, ".BackgroundFlags"); if (bgFlags != NULL) { TBackgroundFlags flags; TBackgroundFilterFades fades; uint size; bgFlags->getArraySize(size); uint i; for (i=0; igetArrayNode(&flag, i); flag->getValueByName(flags.Flags[i], ".InitialState"); uint32 fadeIn, fadeOut; flag->getValueByName(fadeIn, ".FadeIn"); flag->getValueByName(fadeOut, ".FadeOut"); fades.FadeIns[i] = fadeIn; fades.FadeOuts[i] = fadeOut; flag->getValueByName(_BackgroundFilterNames[i], ".Name"); flag->getValueByName(_BackgroundFilterShortNames[i], ".ShortName"); } for (; i< TBackgroundFlags::NB_BACKGROUND_FLAGS; ++i) { uint32 fadeIn, fadeOut; NLGEORGES::UFormElm::TWhereIsValue where = NLGEORGES::UFormElm::ValueDefaultDfn; root.getValueByName(fadeIn, ".BackgroundFlags[0].FadeIn", NLGEORGES::UFormElm::Eval, &where); root.getValueByName(fadeOut, ".BackgroundFlags[0].FadeOut", NLGEORGES::UFormElm::Eval, &where); root.getValueByName(flags.Flags[i], ".BackgroundFlags[0].InitialState", NLGEORGES::UFormElm::Eval, &where); fades.FadeIns[i] = fadeIn; fades.FadeOuts[i] = fadeOut; } setBackgroundFilterFades(fades); setBackgroundFlags(flags); } NLGEORGES::UFormLoader::releaseLoader(formLoader); } } catch(...) { NLGEORGES::UFormLoader::releaseLoader(formLoader); } // init the user var bindings initUserVar(); } /// Build a sample bank from a directory containing .wav files, and return the path to the written file. std::string UAudioMixer::buildSampleBank(const std::string &wavDir, const std::string &bankDir, const std::string &bankName) { vector sampleList; CPath::getPathContent(wavDir, false, false, true, sampleList); // remove any non wav file for (uint j = 0; j < sampleList.size(); ++j) { if (sampleList[j].find(".wav") != sampleList[j].size() - 4) { sampleList.erase(sampleList.begin() + j); --j; } } sort(sampleList.begin(), sampleList.end()); return buildSampleBank(sampleList, bankDir, bankName); } /// Build a sample bank from a list of .wav files, and return the path to the written file. std::string UAudioMixer::buildSampleBank(const std::vector &sampleList, const std::string &bankDir, const std::string &bankName) { // need to create a new bank file ! CAudioMixerUser::TSampleBankHeader hdr; vector > adpcmBuffers(sampleList.size()); vector > mono16Buffers(sampleList.size()); for (uint j = 0; j < sampleList.size(); ++j) { nldebug(" Adding sample [%s] into bank", CFile::getFilename(sampleList[j]).c_str()); CIFile sample(sampleList[j]); uint size = sample.getFileSize(); std::vector buffer; buffer.resize(size); sample.serialBuffer(&buffer[0], sample.getFileSize()); std::vector result; IBuffer::TBufferFormat bufferFormat; uint8 channels; uint8 bitsPerSample; uint32 frequency; if (!IBuffer::readWav(&buffer[0], size, result, bufferFormat, channels, bitsPerSample, frequency)) { nlwarning(" IBuffer::readWav returned false"); continue; } vector mono16Data; if (!IBuffer::convertToMono16PCM(&result[0], (uint)result.size(), mono16Data, bufferFormat, channels, bitsPerSample)) { nlwarning(" IBuffer::convertToMono16PCM returned false"); continue; } vector adpcmData; if (!IBuffer::convertMono16PCMToMonoADPCM(&mono16Data[0], (uint)mono16Data.size(), adpcmData)) { nlwarning(" IBuffer::convertMono16PCMToMonoADPCM returned false"); continue; } // Sample number MUST be even nlassert(mono16Data.size() == (mono16Data.size() & 0xfffffffe)); nlassert(adpcmData.size() == mono16Data.size() / 2); adpcmBuffers[j].swap(adpcmData); mono16Buffers[j].swap(mono16Data); hdr.addSample(CFile::getFilename(sampleList[j]), frequency, (uint32)mono16Data.size(), (uint32)mono16Buffers[j].size() * 2, (uint32)adpcmBuffers[j].size()); } // write the sample bank (if any sample available) if (!hdr.Name.empty()) { string filename = CPath::standardizePath(bankDir, true) + bankName + ".sample_bank"; COFile sbf(filename); sbf.serial(hdr); // nldebug("Header seeking = %i", sbf.getPos()); nlassert(mono16Buffers.size() == adpcmBuffers.size()); for (uint j = 0; j < mono16Buffers.size(); ++j) { sbf.serialBuffer((uint8*)(&mono16Buffers[j][0]), (uint)mono16Buffers[j].size()*2); sbf.serialBuffer((uint8*)(&adpcmBuffers[j][0]), (uint)adpcmBuffers[j].size()); } return filename; } else { return ""; } } void CAudioMixerUser::buildSampleBankList() { uint i; // regenerate the sample banks list const std::string &sbp = _SampleBankPath; const std::string &swp = _SampleWavPath; // build the list of available sample bank directory vector bankDir; CPath::getPathContent(swp, false, true, false, bankDir); sort(bankDir.begin(), bankDir.end()); for (i = 0; i < bankDir.size(); ++i) { if (bankDir[i].empty()) { bankDir.erase(bankDir.begin()+i); --i; } } for (i = 0; i < bankDir.size(); ++i) { nldebug("Found sample bank dir [%s]", bankDir[i].c_str()); } // build the list of available sample bank file vector bankFile; CPath::getPathContent(sbp, false, false, true, bankFile); // filter out any non sample bank file for (i = 0; i < bankFile.size(); ++i) { if (bankFile[i].find(".sample_bank") != bankFile[i].size() - 12) { bankFile.erase(bankFile.begin()+i); --i; } } sort(bankFile.begin(), bankFile.end()); for (i = 0; i < bankFile.size(); ++i) { nldebug("Found sample bank file [%s]", bankFile[i].c_str()); } // now, do a one to one comparison on bank file and sample file date for (i = 0; i < bankDir.size(); ++i) { string bankname = bankDir[i]; if (bankname[bankname.size()-1] == '/') bankname = bankname.substr(0, bankname.size()-1); bankname = bankname.substr(bankname.rfind('/')+1); if (i >= bankFile.size() || CFile::getFilenameWithoutExtension(bankFile[i]) > bankname) { nlinfo("Compiling sample bank [%s]", bankname.c_str()); std::string filename = buildSampleBank(bankDir[i], sbp, bankname); if (bankFile.size() < i + 1) bankFile.resize(i + 1); else bankFile.insert(bankFile.begin() + i, std::string()); bankFile[i] = filename; } else if (bankname < CFile::getFilenameWithoutExtension(bankDir[i])) { nlinfo("Removing sample bank file [%s]", bankname.c_str()); // remove an out of date bank file CFile::deleteFile(bankFile[i]); bankFile.erase(bankFile.begin()+i); // recheck on this index --i; } else { bool upToDate = true; // check file list and date nlassert(bankname == CFile::getFilenameWithoutExtension(bankFile[i])); // read the sample bank file header. try { CIFile sbf(bankFile[i]); TSampleBankHeader hdr; sbf.serial(hdr); vector sampleList; CPath::getPathContent(bankDir[i], false, false, true, sampleList); sort(sampleList.begin(), sampleList.end()); if (sampleList.size() == hdr.Name.size()) { for (uint j=0; j= CFile::getFileModificationDate(bankFile[i])) { upToDate = false; break; } } } } catch(const Exception &) { upToDate = false; } if (!upToDate) { nlinfo("Need to update bank file [%s]", bankname.c_str()); CFile::deleteFile(bankFile[i]); bankFile.erase(bankFile.begin()+i); // recheck on this index --i; } } } // clear any out of date bank file for (; i fileList; CPath::getPathContent(sp, false, false, true, fileList); for (uint i=0; i dirList; if(!sp.empty()) CPath::getPathContent(sp, false, true, false, dirList); while (!dirList.empty()) { nldebug("Generating sample bank list for %s", dirList.back().c_str()); std::vector sampleList; CPath::getPathContent(dirList.back(), true, false, true, sampleList); for (uint i=0; i< sampleList.size(); ++i) { sampleList[i] = CFile::getFilename(sampleList[i]); nldebug("+- Adding sample %s to bank", sampleList[i].c_str()); } std::vector temp; NLMISC::explode(dirList.back(), "/", temp, true); nlassert(!temp.empty()); std::string listName(temp.back()); COFile file(_SamplePath+listName+SampleBankListExt); file.serialCont(sampleList); dirList.pop_back(); } */ // update the searh path content bool compressed = CPath::isMemoryCompressed(); if (!compressed) CPath::addSearchPath(sbp); } /// Build the sound bank packed sheets file from georges sound sheet files with .sound extension in the search path, and return the path to the written file. std::string UAudioMixer::buildSoundBank(const std::string &packedSheetDir) { std::string dir = CPath::standardizePath(packedSheetDir, true); CSoundBank *soundBank = new CSoundBank(); soundBank->load(dir, true); delete soundBank; return dir + "sounds.packed_sheets"; } void CAudioMixerUser::setBackgroundFlagName(uint flagIndex, const std::string &flagName) { if (flagIndex < TBackgroundFlags::NB_BACKGROUND_FLAGS) _BackgroundFilterNames[flagIndex] = flagName; } void CAudioMixerUser::setBackgroundFlagShortName(uint flagIndex, const std::string &flagShortName) { if (flagIndex < TBackgroundFlags::NB_BACKGROUND_FLAGS) _BackgroundFilterShortNames[flagIndex] = flagShortName; } const std::string &CAudioMixerUser::getBackgroundFlagName(uint flagIndex) { static std::string bad(""); if (flagIndex < TBackgroundFlags::NB_BACKGROUND_FLAGS) return _BackgroundFilterNames[flagIndex]; else return bad; } const std::string &CAudioMixerUser::getBackgroundFlagShortName(uint flagIndex) { static std::string bad(""); if (flagIndex < TBackgroundFlags::NB_BACKGROUND_FLAGS) return _BackgroundFilterShortNames[flagIndex]; else return bad; } const UAudioMixer::TBackgroundFlags &CAudioMixerUser::getBackgroundFlags() { return _BackgroundSoundManager->getBackgroundFlags(); } const UAudioMixer::TBackgroundFilterFades &CAudioMixerUser::getBackgroundFilterFades() { return _BackgroundSoundManager->getBackgroundFilterFades(); } class CUserVarSerializer { public: std::vector Controlers; void readGeorges (const NLMISC::CSmartPtr &form, const std::string &/* name */) { try { std::string varname, soundName, paramId; NLGEORGES::UFormElm &root = form->getRootNode(); NLGEORGES::UFormElm *items; uint size; CAudioMixerUser::CControledSources cs; // preset the default value cs.Value = 0.0f; root.getValueByName(varname, ".Name"); root.getValueByName(paramId, ".ParamId"); cs.Name = CStringMapper::map(varname); if (paramId == "Gain") cs.ParamId = CAudioMixerUser::gain_control; else if (paramId == "Pitch") cs.ParamId = CAudioMixerUser::pitch_control; else return; root.getNodeByName(&items, ".Sounds"); items->getArraySize(size); for (uint i=0; igetArrayValue(soundName, i); soundName = soundName.substr(0, soundName.find(".sound")); cs.SoundNames.push_back(CStringMapper::map(soundName)); } if (!cs.SoundNames.empty()) Controlers.push_back(cs); } catch(...) {} } void serial (NLMISC::IStream &s) { s.serialCont(Controlers); } void removed() {} static uint getVersion () { return 2; } }; // ****************************************************************** void CAudioMixerUser::initUserVar() { _UserVarControls.clear(); /// Temporary container. std::map Container; // read all *.user_var_binding sheet in data/sound/user_var folder // load the sound_group sheets ::loadForm("user_var_binding", _PackedSheetPath+"user_var_binding.packed_sheets", Container, _UpdatePackedSheet, false); // fill the real container. std::map::iterator first(Container.begin()), last(Container.end()); for (; first != last; ++first) { for (uint i=0; isecond.Controlers.size(); ++i) { _UserVarControls.insert(make_pair(first->second.Controlers[i].Name, first->second.Controlers[i])); } } // update all the sounds to refer to the controler. { TUserVarControlsContainer::iterator first(_UserVarControls.begin()), last(_UserVarControls.end()); for(; first != last; ++first) { std::vector::iterator first2(first->second.SoundNames.begin()), last2(first->second.SoundNames.end()); for (; first2 != last2; ++first2) { CSound *sound = getSoundId(*first2); if (sound != 0) { // ok, the sound exist ! sound->_UserVarControler = first->second.Name; } } } } } // ****************************************************************** void CAudioMixerUser::CControledSources::serial(NLMISC::IStream &s) { std::string name, soundName; if (s.isReading()) { s.serial(name); Name = CStringMapper::map(name); s.serialEnum(ParamId); uint32 size; s.serial(size); for (uint i=0; isecond.Value != value) { it->second.Value = value; // update all sources std::set::iterator first(it->second.Sources.begin()), last(it->second.Sources.end()); for (; first != last; ++first) { if (it->second.ParamId == gain_control) { float relGain = (*first)->getRelativeGain(); float gain = (*first)->getSound()->getGain(); (*first)->setGain(gain * value); (*first)->setRelativeGain(relGain); } else { (*first)->setPitch(value); } } } } } // ****************************************************************** float CAudioMixerUser::getUserVar(NLMISC::TStringId varName) { TUserVarControlsContainer::iterator it(_UserVarControls.find(varName)); if (it != _UserVarControls.end()) { return it->second.Value; } // return a default value. return 1.0f; } // ****************************************************************** void CAudioMixerUser::addUserControledSource(CSourceCommon *source, NLMISC::TStringId varName) { TUserVarControlsContainer::iterator it(_UserVarControls.find(varName)); if (it != _UserVarControls.end()) { // ok, the var exist, insert this source it->second.Sources.insert(source); // update the controled parameter if (it->second.ParamId == gain_control) { float relGain = source->getRelativeGain(); float gain = source->getSound()->getGain(); source->setGain(gain * it->second.Value); source->setRelativeGain(relGain); } else { source->setPitch(it->second.Value); } } } // ****************************************************************** void CAudioMixerUser::removeUserControledSource(CSourceCommon *source, NLMISC::TStringId varName) { TUserVarControlsContainer::iterator it(_UserVarControls.find(varName)); if (it != _UserVarControls.end()) { // ok, the var exist, remove this source it->second.Sources.erase(source); } } // ****************************************************************** void CAudioMixerUser::bufferUnloaded(IBuffer *buffer) { // check all track to find a track playing this buffer. for (uint i = 0; i < _Tracks.size(); ++i) { CTrack *track = _Tracks[i]; if (track && track->getLogicalSource()) { CSourceCommon *src = track->getLogicalSource(); if (src->getType() == CSourceCommon::SOURCE_SIMPLE) { CSimpleSource *simpleSrc = static_cast(src); if (simpleSrc->getBuffer() == buffer) { simpleSrc->stop(); } } } } } // ****************************************************************** void CAudioMixerUser::enable( bool /* b */ ) { // TODO : rewrite this method nlassert(false); /* if ( b ) { // Reenable _NbTracks = _MaxNbTracks; } else { // Disable uint i; for ( i=0; i!=_NbTracks; i++ ) { if ( _Tracks[i] && ! _Tracks[i]->isAvailable() ) { _Tracks[i]->getSource()->leaveTrack(); // nlassert(_PlayingSources.find(_Tracks[i]->getSource()) != _PlayingSources.end()); // _PlayingSources.erase(_Tracks[i]->getSource()); } } _NbTracks = 0; } */ } // ****************************************************************** ISoundDriver* CAudioMixerUser::getSoundDriver() { return _SoundDriver; } // ****************************************************************** void CAudioMixerUser::getFreeTracks( uint nb, CTrack **tracks ) { std::vector::iterator first(_FreeTracks.begin()), last(_FreeTracks.end()); for (nb =0; first != last; ++first, ++nb) { tracks[nb] = *first; } } // ****************************************************************** void CAudioMixerUser::applyListenerMove( const NLMISC::CVector& listenerpos ) { // Store position _ListenPosition = listenerpos; _BackgroundSoundManager->updateBackgroundStatus(); // Environmental effect // computeEnvEffect( listenerpos ); /* // Environment sounds if ( _EnvSounds != NULL ) { _EnvSounds->recompute(); } */ } // ****************************************************************** void CAudioMixerUser::reloadSampleBanks(bool async) { CPath::addSearchPath(_SampleBankPath, true, false); if (_UpdatePackedSheet) buildSampleBankList(); _SampleBankManager->reload(async); } // ****************************************************************** //CTrack *CAudioMixerUser::getFreeTrackWithoutSource(bool steal) //{ // if (!_FreeTracks.empty()) // { // CTrack *free_track = _FreeTracks.back(); // _FreeTracks.pop_back(); // nlassert(!free_track->getLogicalSource()); // ++_ReserveUsage[HighestPri]; // if (_UseEax) free_track->getPhysicalSource()->setEffect(NULL); // no reverb! // return free_track; // } // else if (steal) for (uint i = 0; i < _Tracks.size(); ++i) // { // CSourceCommon *src2 = _Tracks[i]->getLogicalSource(); // if (src2) // { // src2->stop(); // if (_FreeTracks.empty()) // { // nlwarning("No free track after cutting a playing sound source !"); // } // else // { // CTrack *free_track = _FreeTracks.back(); // _FreeTracks.pop_back(); // nlassert(!free_track->getLogicalSource()); // ++_ReserveUsage[HighestPri]; // if (_UseEax) free_track->getPhysicalSource()->setEffect(NULL); // no reverb! // return free_track; // } // } // } // return NULL; //} CTrack *CAudioMixerUser::getFreeTrack(CSourceCommon *source) { // nldebug("There are %d free tracks", _FreeTracks.size() ); // at least some track free ? if (!_FreeTracks.empty()) { // under the low water mark or under the reserve if (_FreeTracks.size() > _LowWaterMark || _ReserveUsage[source->getPriority()] < _PriorityReserve[source->getPriority()] ) { // non discardable track or not too many waiting source if (source->getPriority() == HighestPri || _FreeTracks.size() > _SourceWaitingForPlay.size()) { CTrack *ret = _FreeTracks.back(); _FreeTracks.pop_back(); ret->setLogicalSource(source); _ReserveUsage[source->getPriority()]++; // nldebug("Track %p assign to source %p", ret, ret->getSource()); return ret; } } } // try to find a track with a source cuttable { float srcMinDist = source->getSound()->getMinDistance(); float srcMaxDist = source->getSound()->getMaxDistance(); float d1, d2, t1, t2; d1 = source->getSourceRelativeMode() ? source->getPos().norm() : (source->getPos() - _ListenPosition).norm(); t1 = max(0.0f, 1.0f - ((d1 - srcMinDist) / (srcMaxDist - srcMinDist))); for (uint i = 0; i < _Tracks.size(); ++i) { CSourceCommon *src2 = _Tracks[i]->getLogicalSource(); if (src2) { float src2MinDist = src2->getSound()->getMinDistance(); float src2MaxDist = src2->getSound()->getMaxDistance(); d2 = src2->getSourceRelativeMode() ? src2->getPos().norm() : (src2->getPos() - _ListenPosition).norm(); t2 = max(0.0f, 1.0f - ((d2 - src2MinDist) / (src2MaxDist - src2MinDist))); const float tfactor = 1.3f; if (t1 > t2 * tfactor) // if (d1 < d2) { // nldebug("Cutting source %p with source %p (%f > %f*%f)", src2, source, t1, tfactor, t2); // on peut cuter cette voie ! src2->stop(); if (_FreeTracks.empty()) { nlwarning("No free track after cutting a playing sound source !"); } else { CTrack *ret = _FreeTracks.back(); _FreeTracks.pop_back(); ret->setLogicalSource(source); _ReserveUsage[source->getPriority()]++; // nldebug("Track %p assign to source %p", ret, ret->getSource()); return ret; } } } } } return 0; } // ****************************************************************** //void CAudioMixerUser::freeTrackWithoutSource(CTrack *track) //{ // nlassert(track); // // if (_UseEax) track->getPhysicalSource()->setEffect(_ReverbEffect); // return reverb! // --_ReserveUsage[HighestPri]; // _FreeTracks.push_back(track); //} void CAudioMixerUser::freeTrack(CTrack *track) { nlassert(track != 0); nlassert(track->getLogicalSource() != 0); // nldebug("Track %p free by source %p", track, track->getSource()); _ReserveUsage[track->getLogicalSource()->getPriority()]--; track->setLogicalSource(0); _FreeTracks.push_back(track); } // ****************************************************************** void CAudioMixerUser::getPlayingSoundsPos(bool virtualPos, std::vector > &pos) { int nbplay = 0; int nbmute = 0; int nbsrc = 0; TSourceContainer::iterator first(_Sources.begin()), last(_Sources.end()); for (; first != last; ++first) { CSourceCommon *ps = *first; if (ps->getType() == CSourceCommon::SOURCE_SIMPLE) { CSimpleSource *source = static_cast(*first); nbsrc++; if (source->isPlaying()) { if (virtualPos) pos.push_back(make_pair(source->getTrack() == 0, source->getVirtualPos())); else pos.push_back(make_pair(source->getTrack() == 0, source->getSourceRelativeMode() ? source->getPos() + _ListenPosition : source->getPos())); if (source->getTrack() == 0) nbmute++; else { // nldebug ("Source %p playing on track %p", source, source->getTrack()); nbplay ++; } } } else if (ps->getType() == CSourceCommon::SOURCE_STREAM) { CStreamSource *source = static_cast(*first); nbsrc++; if (source->isPlaying()) { if (virtualPos) pos.push_back(make_pair(source->getTrack() == 0, source->getVirtualPos())); else pos.push_back(make_pair(source->getTrack() == 0, source->getSourceRelativeMode() ? source->getPos() + _ListenPosition : source->getPos())); if (source->getTrack() == 0) nbmute++; else { // nldebug ("Source %p playing on track %p", source, source->getTrack()); nbplay ++; } } } } // nldebug("Total source : %d, playing : %d, muted : %d", nbsrc, nbplay, nbmute); } void CAudioMixerUser::update() { H_AUTO(NLSOUND_AudioMixerUpdate) /* static NLMISC::TTime lastUpdate = NLMISC::CTime::getLocalTime(); NLMISC::TTime now = NLMISC::CTime::getLocalTime(); nldebug("Mixer update : %u ms", uint(now - lastUpdate)); lastUpdate = now; */ #if NL_PROFILE_MIXER TTicks start = CTime::getPerformanceTime(); #endif // update the object. { H_AUTO(NLSOUND_AudioMixerUpdateObjet) // 1st, update the event list { std::vector >::iterator first(_UpdateEventList.begin()), last(_UpdateEventList.end()); for (; first != last; ++first) { if (first->second) { // nldebug("Inserting update %p", first->first); _UpdateList.insert(first->first); } else { // nldebug("Removing update %p", first->first); _UpdateList.erase(first->first); } } _UpdateEventList.clear(); } // 2nd, do the update { TMixerUpdateContainer::iterator first(_UpdateList.begin()), last(_UpdateList.end()); for (; first != last; ++first) { if( *first == 0) { nlwarning("NULL pointeur in update list !"); } else { // call the update method. const IMixerUpdate *update = *first; const_cast(update)->onUpdate(); } } } } // send the event. { H_AUTO(NLSOUND_AudioMixerUpdateSendEvent) // **** 1st, update the event list { std::list >::iterator first(_EventListUpdate.begin()), last(_EventListUpdate.end()); for (; first != last; ++first) { // add an event // nldebug ("Add event %p", first->second); TTimedEventContainer::iterator it(_EventList.insert(make_pair(first->first, NLMISC::CDbgPtr(first->second)))); _Events.insert(make_pair(first->second, it)); } _EventListUpdate.clear(); } // **** 2nd, call the events TTime now = NLMISC::CTime::getLocalTime(); while (!_EventList.empty() && _EventList.begin()->first <= now) { // get the event CAudioMixerUser::IMixerEvent *currentEvent = _EventList.begin()->second; // remove the right entry in the _Events multimap. TEventContainer::iterator it(_Events.lower_bound(_EventList.begin()->second)); while (it->first == _EventList.begin()->second.ptr()) { if (it->second == _EventList.begin()) { _Events.erase(it); break; } it++; } // remove from the _EventList _EventList.erase(_EventList.begin()); // now, run the event // nldebug("Sending Event %p", _EventList.begin()->second); nlassert(currentEvent); currentEvent->onEvent(); #ifdef NL_DEBUG currentEvent = 0; #endif } } // update the background sound _BackgroundSoundManager->updateBackgroundStatus(); // update the background music _BackgroundMusicManager->update(); uint i; // update music channels for (i = 0; i < _NbMusicChannelFaders; ++i) _MusicChannelFaders[i].update(); // Check all playing track and stop any terminated buffer. for (i=0; i<_Tracks.size(); ++i) { if (!_Tracks[i]->isPlaying()) { if (_Tracks[i]->getLogicalSource() != 0) { CSourceCommon *source = _Tracks[i]->getLogicalSource(); source->stop(); } // try to play any waiting source. if (!_SourceWaitingForPlay.empty()) { // check if the source still exist before trying to play it if (_Sources.find(_SourceWaitingForPlay.front()) != _Sources.end()) _SourceWaitingForPlay.front()->play(); // nldebug("Before POP Sources waiting : %u", _SourceWaitingForPlay.size()); _SourceWaitingForPlay.pop_front(); // nldebug("After POP Sources waiting : %u", _SourceWaitingForPlay.size()); } } } if (_ClusteredSound) { H_AUTO(NLSOUND_UpdateClusteredSound) // update the clustered sound... CVector view, up; _Listener.getOrientation(view, up); _ClusteredSound->update(_ListenPosition, view, up); // update all playng track according to there cluster status for (i=0; i<_Tracks.size(); ++i) { if (_Tracks[i]->isPlaying()) { if (_Tracks[i]->getLogicalSource() != 0) { CSourceCommon *source = _Tracks[i]->getLogicalSource(); if (source->getCluster() != 0) { // need to check the cluster status const CClusteredSound::CClusterSoundStatus *css = _ClusteredSound->getClusterSoundStatus(source->getCluster()); if (css != 0) { // there is some data here, update the virtual position of the sound. float dist = (css->Position - source->getPos()).norm(); CVector vpos(_ListenPosition + css->Direction * (css->Dist + dist)); // _Tracks[i]->DrvSource->setPos(source->getPos() * (1-css->PosAlpha) + css->Position*(css->PosAlpha)); _Tracks[i]->getPhysicalSource()->setPos(source->getPos() * (1-css->PosAlpha) + vpos*(css->PosAlpha)); // update the relative gain _Tracks[i]->getPhysicalSource()->setGain(source->getRelativeGain()*source->getGain()*css->Gain); #if EAX_AVAILABLE == 1 if (_UseEax) { H_AUTO(NLSOUND_SetEaxProperties) // update the occlusion parameters _Tracks[i]->DrvSource->setEAXProperty(DSPROPERTY_EAXBUFFER_OCCLUSION, (void*)&css->Occlusion, sizeof(css->Occlusion)); _Tracks[i]->DrvSource->setEAXProperty(DSPROPERTY_EAXBUFFER_OCCLUSIONLFRATIO, (void*)&css->OcclusionLFFactor, sizeof(css->OcclusionLFFactor)); // if (lastRatio[i] != css->OcclusionRoomRatio) // { _Tracks[i]->DrvSource->setEAXProperty(DSPROPERTY_EAXBUFFER_OCCLUSIONROOMRATIO, (void*)&css->OcclusionRoomRatio, sizeof(css->OcclusionRoomRatio)); // lastRatio[i] = css->OcclusionRoomRatio; // nldebug("Setting room ration."); // } _Tracks[i]->DrvSource->setEAXProperty(DSPROPERTY_EAXBUFFER_OBSTRUCTION, (void*)&css->Obstruction, sizeof(css->Obstruction)); } #endif } } } } } } // Debug info /*uint32 i; nldebug( "List of the %u tracks", _NbTracks ); for ( i=0; i!=_NbTracks; i++ ) { CSimpleSource *su; if ( su = _Tracks[i]->getSource() ) { nldebug( "%u: %p %s %s %s %s, vol %u", i, &_Tracks[i]->DrvSource, _Tracks[i]->isAvailable()?"FREE":"USED", _Tracks[i]->isAvailable()?"":(su->isPlaying()?"PLAYING":"STOPPED"), _Tracks[i]->isAvailable()?"":PriToCStr[su->getPriority()], _Tracks[i]->isAvailable()?"":(su->getSound()?su->getSound()->getFilename().c_str():""), (uint)(su->getGain()*100.0f) ); } }*/ _SoundDriver->commit3DChanges(); #if NL_PROFILE_MIXER _UpdateTime = CTime::ticksToSecond(CTime::getPerformanceTime() - start); _UpdateCount++; #endif /* // display the track using... { char tmp[2048] = ""; string str; for (uint i=0; i<_NbTracks/2; ++i) { sprintf(tmp, "[%2u]%8p ", i, _Tracks[i]->getSource()); str += tmp; } nldebug((string("Status1: ")+str).c_str()); str = ""; for (i=_NbTracks/2; i<_NbTracks; ++i) { sprintf(tmp, "[%2u]%8p ", i, _Tracks[i]->getSource()); str += tmp; } // nldebug((string("Status2: ")+str).c_str()); } */ } // ****************************************************************** TSoundId CAudioMixerUser::getSoundId( const NLMISC::TStringId &name ) { return _SoundBank->getSound(name); } // ****************************************************************** void CAudioMixerUser::addSource( CSourceCommon *source ) { nlassert(_Sources.find(source) == _Sources.end()); _Sources.insert( source ); // _profile(( "AM: ADDSOURCE, SOUND: %d, TRACK: %p, NAME=%s", source->getSound(), source->getTrack(), // source->getSound() && (source->getSound()->getName()!="") ? source->getSound()->getName().c_str() : "" )); } static bool checkSound(CSound *sound, const vector > &subsounds, vector &missingFiles) { vector >::const_iterator first(subsounds.begin()), last(subsounds.end()); for (; first != last; ++first) { if (first->second == sound) return false; if (first->second == 0 && !first->first.empty()) missingFiles.push_back(first->first); else if (first->second != 0) { vector > v2; first->second->getSubSoundList(v2); if (!checkSound(sound, v2, missingFiles)) return false; } } return true; } bool CAudioMixerUser::tryToLoadSampleBank(const std::string &sampleName) { string path = CPath::lookup(sampleName, false, false, false); if (!path.empty()) { // extract samplebank name path = NLMISC::CFile::getPath(path); vector rep; explode(path, string("/"), rep, true); loadSampleBank(false, rep.back()); return true; } else { nlwarning("tryToLoadSoundBank : can't find sample bank for '%s'", sampleName.c_str()); return false; } } // ****************************************************************** USource *CAudioMixerUser::createSource( TSoundId id, bool spawn, TSpawnEndCallback cb, void *userParam, NL3D::CCluster *cluster, CSoundContext *context ) { #if NL_PROFILE_MIXER TTicks start = CTime::getPerformanceTime(); #endif _profile(( "AM: [%u]---------------------------------------------------------------", curTime() )); _profile(( "AM: CREATESOURCE: SOUND=%p, NAME=%s, TIME=%d", id, id->getName().c_str(), curTime() )); _profile(( "AM: SOURCES: %d, PLAYING: %d, TRACKS: %d", getSourcesNumber(), getPlayingSourcesNumber(), getNumberAvailableTracks() )); if ( id == NULL ) { _profile(("AM: FAILED CREATESOURCE")); // nldebug( "AM: Sound not created: invalid sound id" ); return NULL; } USource *ret = NULL; if (_AutoLoadSample) { if (id->getSoundType() == CSound::SOUND_SIMPLE) { CSimpleSound *ss = (CSimpleSound*)id; if (ss->getBuffer() == NULL) { const string sampleName = CStringMapper::unmap(ss->getBuffername()) + ".wav"; tryToLoadSampleBank(sampleName); } } else { uint32 count = 0; retrySound: ++count; vector > subsounds; id->getSubSoundList(subsounds); vector missingFiles; // check the sound before anythink else bool invalid = !checkSound(id, subsounds, missingFiles); if (invalid) { nlwarning("The sound %s contain an infinite recursion !", CStringMapper::unmap(id->getName()).c_str()); return NULL; } if (!missingFiles.empty()/* && count <= missingFiles.size()*/) { // try to load missing sample bank for (uint i=0; igetSoundType()) { case CSound::SOUND_SIMPLE: { CSimpleSound *simpleSound = static_cast(id); // This is a simple sound if (simpleSound->getBuffer() == NULL) { static std::set warned; const std::string &name = CStringMapper::unmap(simpleSound->getBuffername()); if (warned.find(name) == warned.end()) { nlwarning ("Can't create the sound '%s'", name.c_str()); warned.insert(name); } return NULL; } // Create source CSimpleSource *source = new CSimpleSource( simpleSound, spawn, cb, userParam, cluster); // nldebug("Mixer : source %p created", source); //if (source->getBuffer() != 0) //{ // // Link the position to the listener position if it'a stereo source // if ( source->getBuffer()->isStereo() ) // { // source->set3DPositionVector( &_ListenPosition ); // } // // no, we don't, there's setSourceRelativeMode for that -_- //} //else //{ // nlassert(false); // FIXME //} // FIXED [KAETEMI] ret = source; } break; case CSound::SOUND_STREAM: { CStreamSound *streamSound = static_cast(id); // This is a stream thingy. ret = new CStreamSource(streamSound, spawn, cb, userParam, cluster); } break; case CSound::SOUND_COMPLEX: { CComplexSound *complexSound = static_cast(id); // This is a pattern sound. ret = new CComplexSource(complexSound, spawn, cb, userParam, cluster); } break; case CSound::SOUND_BACKGROUND: { // This is a background sound. CBackgroundSound *bgSound = static_cast(id); ret = new CBackgroundSource(bgSound, spawn, cb, userParam, cluster); } break; case CSound::SOUND_MUSIC: { // This is a background music sound CMusicSound *music_sound= static_cast(id); ret = new CMusicSource(music_sound, spawn, cb, userParam, cluster); } break; case CSound::SOUND_CONTEXT: { static CSoundContext defaultContext; // This is a context sound. if (context == 0) context = &defaultContext; CContextSound *ctxSound = static_cast(id); CSound *sound = ctxSound->getContextSound(*context); if (sound != 0) { ret = createSource(sound, spawn, cb, userParam, cluster); // Set the volume of the source according to the context volume if (ret != 0) { ret->setGain(ret->getGain() * ctxSound->getGain()); float pitch = ret->getPitch() * ctxSound->getPitch(); ret->setPitch(pitch); } } else ret = 0; } break; default: { // nlassertex(false, ("Unknown sound class !")); nlwarning("Unknow sound class : %u", id->getSoundType()); } break; } #if NL_PROFILE_MIXER _CreateTime = CTime::ticksToSecond(CTime::getPerformanceTime() - start); _CreateCount++; #endif //nldebug( "AM: Source created" ); return ret; } // ****************************************************************** USource *CAudioMixerUser::createSource( const NLMISC::TStringId &name, bool spawn, TSpawnEndCallback cb, void *userParam, NL3D::CCluster *cluster, CSoundContext *context) { return createSource( getSoundId( name ), spawn, cb, userParam, cluster, context); } // ****************************************************************** void CAudioMixerUser::removeSource( CSourceCommon *source ) { nlassert( source != NULL ); size_t n = _Sources.erase(source); nlassert(n == 1); } // ****************************************************************** void CAudioMixerUser::selectEnvEffects( const std::string & tag) { // for testing purposes only _ReverbEffect->setEnvironment(_Environments[CStringMapper::map(tag)]); //nlassertex(false, ("Not implemented yet")); /* // Select Env vector::iterator ipe; for ( ipe=_EnvEffects.begin(); ipe!=_EnvEffects.end(); ++ipe ) { (*ipe)->selectEnv( tag ); } // Compute CVector pos; _Listener.getPos( pos ); computeEnvEffect( pos, true ); */ } // ****************************************************************** /* void CAudioMixerUser::loadEnvEffects( const char *filename ) { nlassert( filename != NULL ); nlinfo( "Loading environmental effects from %s...", filename ); // Unload previous env effects vector::iterator ipe; for ( ipe=_EnvEffects.begin(); ipe!=_EnvEffects.end(); ++ipe ) { delete (*ipe); } _EnvEffects.clear(); string str = CPath::lookup( filename, false ); // Load env effects CIFile file; if ( !str.empty() && file.open(str) ) { uint32 n = CEnvEffect::load( _EnvEffects, file ); nldebug( "AM: Loaded %u environmental effects", n ); } else { nlwarning( "AM: Environmental effects file not found" ); } } */ // ****************************************************************** uint32 CAudioMixerUser::loadSampleBank(bool async, const std::string &name, std::vector *notfoundfiles ) { // nlassert( filename != NULL ); // string path = _SamplePath; // path.append("/").append(filename); //nldebug( "Loading samples bank %s...", name.c_str() ); TStringId nameId = CStringMapper::map(name); CSampleBank* bank = _SampleBankManager->findSampleBank(nameId); if (bank == NULL) { // create a new sample bank bank = new CSampleBank(nameId, _SampleBankManager); } try { bank->load(async); } catch (const Exception& e) { if (notfoundfiles) { notfoundfiles->push_back(name); } string reason = e.what(); nlwarning( "AM: Failed to load the samples: %s", reason.c_str() ); } return bank->countSamples(); } bool CAudioMixerUser::unloadSampleBank(const std::string &name) { // string path = _SamplePath; // path.append("/").append(filename); //nldebug( "Unloading samples bank %s...", name.c_str() ); CSampleBank *pbank = _SampleBankManager->findSampleBank(CStringMapper::map(name)); if (pbank != NULL) { // ok, the bank exist. return pbank->unload(); } else return false; } // ****************************************************************** void CAudioMixerUser::getSoundNames( std::vector &names ) const { _SoundBank->getNames(names); } // ****************************************************************** uint CAudioMixerUser::getPlayingSourcesCount() const { return _PlayingSources; } // ****************************************************************** uint CAudioMixerUser::getAvailableTracksCount() const { return (uint)_FreeTracks.size(); } uint CAudioMixerUser::getUsedTracksCount() const { return (uint)_Tracks.size() - (uint)_FreeTracks.size(); } // ****************************************************************** string CAudioMixerUser::getSourcesStats() const { // TODO : rewrite log output string s; TSourceContainer::const_iterator ips; for ( ips=_Sources.begin(); ips!=_Sources.end(); ++ips ) { if ( (*ips)->isPlaying() ) { // char line [80]; /* nlassert( (*ips)->getSound() && (*ips)->getSimpleSound()->getBuffer() ); smprintf( line, 80, "%s: %u%% %s %s", (*ips)->getSound()->getName().c_str(), (uint32)((*ips)->getGain()*100.0f), (*ips)->getBuffer()->isStereo()?"ST":"MO", PriToCStr[(*ips)->getPriority()] ); s += string(line) + "\n"; */ } } return s; } // ****************************************************************** /* void CAudioMixerUser::loadEnvSounds( const char *filename, UEnvSound **treeRoot ) { nlassert( filename != NULL ); nlinfo( "Loading environment sounds from %s...", filename ); string str = CPath::lookup( filename, false ); CIFile file; if ( !str.empty() && file.open( str ) ) { uint32 n = 0; //CEnvSoundUser::load( _EnvSounds, file ); nldebug( "AM: Loaded %u environment sounds", n ); } else { nlwarning( "AM: Environment sounds file not found: %s", filename ); } if ( treeRoot != NULL ) { *treeRoot = _EnvSounds; } } */ // ****************************************************************** struct CompareSources : public binary_function { // Constructor CompareSources( const CVector &pos ) : _Pos(pos) {} // Operator() bool operator()( const CSimpleSource *s1, const CSimpleSource *s2 ) { if (s1->getPriority() < s2->getPriority()) { return true; } else if (s1->getPriority() == s2->getPriority()) { // Equal priority, test distances to the listener const CVector &src1pos = s1->getPos(); const CVector &src2pos = s2->getPos();; return ( (src1pos-_Pos).sqrnorm() < (src2pos-_Pos).sqrnorm() ); } else { return false; } } // Listener pos const CVector &_Pos; }; // ****************************************************************** uint32 CAudioMixerUser::getLoadedSampleSize() { return _SampleBankManager->getTotalByteSize(); } void CAudioMixerUser::getLoadedSampleBankInfo(std::vector > &result) { _SampleBankManager->getLoadedSampleBankInfo(result); } void CAudioMixerUser::setListenerPos (const NLMISC::CVector &pos) { _Listener.setPos(pos); _BackgroundSoundManager->setListenerPosition(pos); } NLMISC_CATEGORISED_COMMAND(nel, displaySoundInfo, "Display information about the audio mixer", "") { nlunreferenced(rawCommandString); nlunreferenced(quiet); nlunreferenced(human); if(args.size() != 0) return false; if (CAudioMixerUser::instance() == NULL) { log.displayNL ("No audio mixer available"); return true; } log.displayNL ("%d tracks, MAX_TRACKS = %d, contains:", CAudioMixerUser::instance()->_Tracks.size(), CAudioMixerUser::instance()->getSoundDriver()->countMaxSources()); for (uint i = 0; i < CAudioMixerUser::instance()->_Tracks.size(); i++) { if (CAudioMixerUser::instance()->_Tracks[i] == NULL) { log.displayNL ("Track %d is NULL", i); } else { log.displayNL ("Track %d %s available and %s playing.", i, (CAudioMixerUser::instance()->_Tracks[i]->isAvailable()?"is":"is not"), (CAudioMixerUser::instance()->_Tracks[i]->isPlaying()?"is":"is not")); if (CAudioMixerUser::instance()->_Tracks[i]->getLogicalSource() == NULL) { log.displayNL (" CSourceCommon is NULL"); } else { const CVector &pos = CAudioMixerUser::instance()->_Tracks[i]->getLogicalSource()->getPos(); string bufname; CSourceCommon *src = CAudioMixerUser::instance()->_Tracks[i]->getLogicalSource(); switch (src->getType()) { case CSourceCommon::SOURCE_SIMPLE: { CSimpleSource *simpleSrc = static_cast(src); if (simpleSrc->getBuffer()) bufname = CStringMapper::unmap(simpleSrc->getBuffer()->getName()); log.displayNL(" CSourceCommon is CSimpleSource is id %d buffer name '%s' pos %f %f %f", simpleSrc->getSound(), bufname.c_str(), pos.x, pos.y, pos.z); } break; default: log.displayNL(" CSourceCommon is id %d pos %f %f %f", src->getSound(), pos.x, pos.y, pos.z); break; } } } } return true; } void CAudioMixerUser::registerBufferAssoc(CSound *sound, IBuffer *buffer) { _BufferToSources[buffer].push_back(sound); } void CAudioMixerUser::unregisterBufferAssoc(CSound *sound, IBuffer *buffer) { TBufferToSourceContainer::iterator it(_BufferToSources.find(buffer)); if (it != _BufferToSources.end()) { std::vector::iterator first(it->second.begin()), last(it->second.end()); for (; first != last; ++first) { if (*first == sound) { it->second.erase(first); break; } } } } /// Register an object in the update list. void CAudioMixerUser::registerUpdate(CAudioMixerUser::IMixerUpdate *pmixerUpdate) { // nldebug("Registering update %p", pmixerUpdate); nlassert(pmixerUpdate != 0); _UpdateEventList.push_back(make_pair(pmixerUpdate, true)); } /// Unregister an object from the update list. void CAudioMixerUser::unregisterUpdate(CAudioMixerUser::IMixerUpdate *pmixerUpdate) { // nldebug("Unregistering update %p", pmixerUpdate); nlassert(pmixerUpdate != 0); _UpdateEventList.push_back(make_pair(pmixerUpdate, false)); } /// Add an event in the future. void CAudioMixerUser::addEvent( CAudioMixerUser::IMixerEvent *pmixerEvent, const NLMISC::TTime &date) { nlassert(pmixerEvent != 0); // nldebug("Adding event %p", pmixerEvent); _EventListUpdate.push_back(make_pair(date, pmixerEvent)); } /// Remove any event programmed for this object. void CAudioMixerUser::removeEvents( CAudioMixerUser::IMixerEvent *pmixerEvent) { nlassert(pmixerEvent != 0); // nldebug("Removing event %p", pmixerEvent); // we have to remove from the _EventListUpdate, in the case a IMixerEvent is // added/removed during the same frame!!! // Slow O(N) but _EventListUpdate should be small, cause cleared each frame and not so // many events are added/removed. std::list >::iterator itUp; for(itUp=_EventListUpdate.begin(); itUp!=_EventListUpdate.end();) { if(itUp->second == pmixerEvent) itUp= _EventListUpdate.erase(itUp); else itUp++; } // remove from the both _EventList and _Events multimap pair range = _Events.equal_range(pmixerEvent); TEventContainer::iterator first(range.first), last(range.second); for (; first != last; ++first) { _EventList.erase(first->second); } _Events.erase(range.first, range.second); } void CAudioMixerUser::setBackgroundFlags(const TBackgroundFlags &backgroundFlags) { _BackgroundSoundManager->setBackgroundFlags(backgroundFlags); } void CAudioMixerUser::setBackgroundFilterFades(const TBackgroundFilterFades &backgroundFilterFades) { _BackgroundSoundManager->setBackgroundFilterFades(backgroundFilterFades); } //void CAudioMixerUser::loadBackgroundSoundFromRegion (const NLLIGO::CPrimRegion ®ion) //{ // _BackgroundSoundManager->loadSoundsFromRegion(region); //} //void CAudioMixerUser::loadBackgroundEffectsFromRegion (const NLLIGO::CPrimRegion ®ion) //{ // _BackgroundSoundManager->loadEffecsFromRegion(region); //} //void CAudioMixerUser::loadBackgroundSamplesFromRegion (const NLLIGO::CPrimRegion ®ion) //{ // _BackgroundSoundManager->loadSamplesFromRegion(region); //} void CAudioMixerUser::loadBackgroundAudioFromPrimitives(const NLLIGO::IPrimitive &audioRoot) { _BackgroundSoundManager->loadAudioFromPrimitives(audioRoot); } void CAudioMixerUser::playBackgroundSound () { _BackgroundSoundManager->play (); } void CAudioMixerUser::stopBackgroundSound () { _BackgroundSoundManager->stop (); } void CAudioMixerUser::loadBackgroundSound (const std::string &continent, NLLIGO::CLigoConfig &config) { _BackgroundSoundManager->load (continent, config); } void CAudioMixerUser::startDriverBench() { if (_SoundDriver) _SoundDriver->startBench(); } void CAudioMixerUser::endDriverBench() { if (_SoundDriver) _SoundDriver->endBench(); } void CAudioMixerUser::displayDriverBench(CLog *log) { if (_SoundDriver) _SoundDriver->displayBench(log); } // *************************************************************************** void CAudioMixerUser::changeMaxTrack(uint maxTrack) { uint max_track_old = maxTrack; maxTrack = min(maxTrack, _SoundDriver->countMaxSources()); if (maxTrack != max_track_old) nlwarning("AM: MaxTrack limited from %u to %u", (uint32)max_track_old, (uint32)maxTrack); // if same, no op if (maxTrack == _Tracks.size()) return; uint prev_track_nb = (uint)_Tracks.size(); // **** if try to add new tracks, easy if (maxTrack > prev_track_nb) { uint i = 0; _Tracks.resize(maxTrack, NULL); try { for (i = prev_track_nb; i < maxTrack; ++i) { nlassert(!_Tracks[i]); _Tracks[i] = new CTrack(); _Tracks[i]->init(_SoundDriver); if (_UseEax) _Tracks[i]->getPhysicalSource()->setEffect(_ReverbEffect); // insert in front because the last inserted wan be sofware buffer... _FreeTracks.insert(_FreeTracks.begin(), _Tracks[i]); } } catch (const ESoundDriver &) { delete _Tracks[i]; // If the source generation failed, keep only the generated number of sources maxTrack = i; _Tracks.resize(maxTrack); nlwarning("AM: Failed to create another track, MaxTrack is %u now", (uint32)maxTrack); } } // **** else must delete some tracks else { vector non_erasable; while (_Tracks.size() + non_erasable.size() > maxTrack && _Tracks.size() > 0) { CTrack *track = _Tracks.back(); _Tracks.pop_back(); nlassert(track); if (track->isAvailable()) { _FreeTracks.erase(find(_FreeTracks.begin(), _FreeTracks.end(), track)); delete track; } else if (track->getLogicalSource()) { track->getLogicalSource()->stop(); if (track->getLogicalSource()) { nlwarning("AM: cant stop a track"); non_erasable.push_back(track); } else { _FreeTracks.erase(find(_FreeTracks.begin(), _FreeTracks.end(), track)); delete track; } } else /* music track or something */ { non_erasable.push_back(track); } } while (non_erasable.size() > 0) { // put non erasable back into track list _Tracks.push_back(non_erasable.back()); non_erasable.pop_back(); } if (maxTrack != _Tracks.size()) nlwarning("AM: Failed to reduce number of tracks; MaxTrack is now %u instead of the requested %u", (uint32)_Tracks.size(), (uint32)maxTrack); } } // *************************************************************************** // insert calls of this where you want to debug events void CAudioMixerUser::debugLogEvent(const char *reason) { nlinfo("****** EVENTLOG: end of %s", reason); nlinfo("****** _EventListUpdate: %d", _EventListUpdate.size()); std::list >::const_iterator itUp; for(itUp=_EventListUpdate.begin();itUp!=_EventListUpdate.end();itUp++) { nlinfo("\t: %d - %x", (uint32)itUp->first, itUp->second); } nlinfo("****** _EventList: %d", _EventList.size()); TTimedEventContainer::const_iterator it; for(it=_EventList.begin();it!=_EventList.end();it++) { nlinfo("\t: %d - %x", (uint32)it->first, it->second.ptr()); } nlinfo("****** _Events: %d", _Events.size()); TEventContainer::const_iterator itEv; for(itEv=_Events.begin();itEv!=_Events.end();itEv++) { nlinfo("\t: %x - (%d,%x)", itEv->first, (uint32)itEv->second->first, itEv->second->second.ptr()); } } // *************************************************************************** bool CAudioMixerUser::playMusicChannel(TMusicChannel chan, const std::string &fileName, uint xFadeTime, bool async, bool loop) { if (_MusicChannelFaders[chan].isInitOk()) return _MusicChannelFaders[chan].play(fileName, xFadeTime, async, loop); return false; } // *************************************************************************** bool CAudioMixerUser::playMusic(const std::string &fileName, uint xFadeTime, bool async, bool loop) { return playMusicChannel(GeneralMusicChannel, fileName, xFadeTime, async, loop); } // *************************************************************************** void CAudioMixerUser::stopMusic(uint xFadeTime) { if (_MusicChannelFaders[GeneralMusicChannel].isInitOk()) _MusicChannelFaders[GeneralMusicChannel].stop(xFadeTime); } // *************************************************************************** void CAudioMixerUser::pauseMusic() { if (_MusicChannelFaders[GeneralMusicChannel].isInitOk()) _MusicChannelFaders[GeneralMusicChannel].pause(); } // *************************************************************************** void CAudioMixerUser::resumeMusic() { if (_MusicChannelFaders[GeneralMusicChannel].isInitOk()) _MusicChannelFaders[GeneralMusicChannel].resume(); } // *************************************************************************** bool CAudioMixerUser::isMusicEnded() { if (_MusicChannelFaders[GeneralMusicChannel].isInitOk()) return _MusicChannelFaders[GeneralMusicChannel].isEnded(); return true; } // *************************************************************************** void CAudioMixerUser::setMusicVolume(float gain) { if (_MusicChannelFaders[GeneralMusicChannel].isInitOk()) _MusicChannelFaders[GeneralMusicChannel].setVolume(gain); } // *************************************************************************** float CAudioMixerUser::getMusicLength() { if (_MusicChannelFaders[GeneralMusicChannel].isInitOk()) return _MusicChannelFaders[GeneralMusicChannel].getLength(); return 0.0f; } // *************************************************************************** bool CAudioMixerUser::getSongTitle(const std::string &filename, std::string &result) { if (_SoundDriver) { std::string artist; std::string title; if (_SoundDriver->getMusicInfo(filename, artist, title)) { if (!title.empty()) { if (!artist.empty()) result = artist + " - " + title; else result = title; } else if (!artist.empty()) { result = artist + " - " + CFile::getFilename(filename); } else result = CFile::getFilename(filename); return true; } } result = "???"; return false; } // *************************************************************************** void CAudioMixerUser::enableBackgroundMusic(bool enable) { getBackgroundMusicManager()->enable(enable); } // *************************************************************************** void CAudioMixerUser::enableBackgroundMusicTimeConstraint(bool enable) { getBackgroundMusicManager()->enableTimeConstraint(enable); } // *************************************************************************** bool CAudioMixerUser::playEventMusic(const std::string &fileName, uint xFadeTime, bool async, bool loop) { return playMusicChannel(EventMusicChannel, fileName, xFadeTime, async, loop); } // *************************************************************************** void CAudioMixerUser::stopEventMusic(uint /* xFadeTime */) { if (_MusicChannelFaders[EventMusicChannel].isInitOk()) _MusicChannelFaders[EventMusicChannel].stop(); } // *************************************************************************** void CAudioMixerUser::setEventMusicVolume(float gain) { if (_MusicChannelFaders[EventMusicChannel].isInitOk()) _MusicChannelFaders[EventMusicChannel].setVolume(gain); } // *************************************************************************** bool CAudioMixerUser::isEventMusicEnded() { if (_MusicChannelFaders[EventMusicChannel].isInitOk()) _MusicChannelFaders[EventMusicChannel].isEnded(); return true; } /// Get audio/container extensions that are currently supported by nel or the used driver implementation. void CAudioMixerUser::getMusicExtensions(std::vector &extensions) { _SoundDriver->getMusicExtensions(extensions); } /// Add a reverb environment void CAudioMixerUser::addEnvironment(const std::string &environmentName, const IReverbEffect::CEnvironment &environment) { if (_ReverbEffect) { TStringId environment_name = CStringMapper::map(environmentName); if (_Environments.find(environment_name) != _Environments.end()) nlwarning("Reverb environment %s already exists, replacing with new one", CStringMapper::unmap(environment_name).c_str()); _Environments[environment_name] = environment; } } /// Set the current reverb environment void CAudioMixerUser::setEnvironment(NLMISC::TStringId environmentName, float roomSize) { if (_ReverbEffect) { _ReverbEffect->setEnvironment(getEnvironment(environmentName), roomSize); } } /// Get a reverb environment const IReverbEffect::CEnvironment &CAudioMixerUser::getEnvironment(NLMISC::TStringId environmentName) { TEnvironments::iterator it(_Environments.find(environmentName)); if (it == _Environments.end()) { nlwarning("Reverb environment '%s' does not exist, returning default", CStringMapper::unmap(environmentName).c_str()); return _DefaultEnvironment; } return it->second; } #if !FINAL_VERSION NLMISC_CATEGORISED_COMMAND(nel, displaySoundProfile, "Display information on sound driver", "") { nlunreferenced(rawCommandString); nlunreferenced(args); nlunreferenced(quiet); nlunreferenced(human); string performance; CAudioMixerUser::getInstance()->writeProfile(performance); vector pv; explode(performance, "\n", pv, true); vector::iterator it(pv.begin()), end(pv.end()); for (; it != end; ++it) log.displayNL((*it).c_str()); return true; } #endif /* !FINAL_VERSION */ } // NLSOUND