Files
watrbx-game-engine/App/util/SoundService.cpp
T
2025-09-18 17:55:52 -04:00

885 lines
27 KiB
C++

/* Copyright 2003-2014 ROBLOX Corporation, All Rights Reserved */
#include "stdafx.h"
#include "Util/SoundService.h"
#include "Util/SoundChannel.h"
#include "V8DataModel/ContentProvider.h"
#include "V8DataModel/DataModel.h"
#include "V8DataModel/Workspace.h"
#include "V8DataModel/PartInstance.h"
#include "V8DataModel/Camera.h"
#include "util/standardout.h"
#include "V8DataModel/GameSettings.h"
#include "V8DataModel/DebugSettings.h"
#include "V8DataModel/PlayerGui.h"
#include "V8DataModel/GameBasicSettings.h"
#include "Network/Players.h"
#include "FastLog.h"
#include "rbx/RbxDbgInfo.h"
FASTINTVARIABLE(FMODSoundChannels, 100);
using namespace RBX;
using namespace RBX::Soundscape;
LOGVARIABLE(FMOD, 0)
LOGVARIABLE(Sound, 0)
DYNAMIC_LOGVARIABLE(SoundTiming, 0)
DYNAMIC_LOGVARIABLE(SoundTrace, 0)
FASTFLAGVARIABLE(DebugBreakOnFMODErrors, true)
namespace
{
void writeFMODLog(const char *error, const std::string &fmodOperation, const void *rbxFmodParent, const void *fmodObject)
{
std::stringstream ss;
ss << "FMOD object: " << fmodObject << ", parent: " << rbxFmodParent << ", fmodOperation: " << fmodOperation << ", error: " << error;
FASTLOGS(FLog::FMOD, "%s", ss.str().c_str());
}
struct FMODSystemRelease
{
void operator()(FMOD::System* system)
{
FASTLOG(FLog::Sound, "Releasing FMOD.");
SoundService::checkResultNoThrow(system->release(), "release", NULL, system);
}
};
} // namespace
namespace RBX
{
namespace Soundscape
{
const char* const sSoundService = "SoundService";
const char* const sSoundChannel = "Sound";
bool SoundService::soundDisabled = false;
} // namespace Soundscape
namespace Reflection
{
template<>
EnumDesc<ReverbType>::EnumDesc()
:EnumDescriptor("ReverbType")
{
addPair(NoReverb, "NoReverb");
addPair(GenericReverb, "GenericReverb");
addPair(PaddedCell, "PaddedCell");
addPair(Room, "Room");
addPair(Bathroom, "Bathroom");
addPair(LivingRoom, "LivingRoom");
addPair(StoneRoom, "StoneRoom");
addPair(Auditorium, "Auditorium");
addPair(ConcertHall, "ConcertHall");
addPair(Cave, "Cave");
addPair(Arena, "Arena");
addPair(Hangar, "Hangar");
addPair(CarpettedHallway, "CarpettedHallway");
addPair(Hallway, "Hallway");
addPair(StoneCorridor, "StoneCorridor");
addPair(Alley, "Alley");
addPair(Forest, "Forest");
addPair(City, "City");
addPair(Mountains, "Mountains");
addPair(Quarry, "Quarry");
addPair(Plain, "Plain");
addPair(ParkingLot, "ParkingLot");
addPair(SewerPipe, "SewerPipe");
addPair(UnderWater, "UnderWater");
}
template<>
EnumDesc<ListenerType>::EnumDesc()
:EnumDescriptor("ListenerType")
{
addPair(CameraListener, "Camera");
addPair(CFrame, "CFrame");
addPair(ObjectPosition, "ObjectPosition");
addPair(ObjectCFrame, "ObjectCFrame");
}
template<>
RBX::Soundscape::ListenerType& Variant::convert<RBX::Soundscape::ListenerType>(void)
{
return genericConvert<RBX::Soundscape::ListenerType>();
}
} //namespace Reflection
template<>
bool StringConverter<RBX::Soundscape::ListenerType>::convertToValue(const std::string& text, RBX::Soundscape::ListenerType& value)
{
return Reflection::EnumDesc<RBX::Soundscape::ListenerType>::singleton().convertToValue(text.c_str(),value);
}
} //namespace RBX
static const Time::Interval gcTime(5); // GC the sounds every 5 seconds
static const float decay = 0.7;
Reflection::BoundProp<float> SoundService::prop_dopplerscale("DopplerScale", category_Data, &SoundService::dopplerscale, &SoundService::on3DSettingChanged);
Reflection::BoundProp<float> SoundService::prop_distancefactor("DistanceFactor", category_Data, &SoundService::distancefactor, &SoundService::on3DSettingChanged);
Reflection::BoundProp<float> SoundService::prop_rolloffscale("RolloffScale", category_Data, &SoundService::rolloffscale, &SoundService::on3DSettingChanged);
Reflection::EnumPropDescriptor<SoundService, ReverbType> SoundService::prop_AmbientReverb("AmbientReverb", category_Data, &SoundService::getAmbientReverb, &SoundService::setAmbientReverb);
Reflection::BoundFuncDesc<SoundService, void(SoundType)> func_playSound(&SoundService::playSound, "PlayStockSound", "sound", Security::RobloxScript);
Reflection::BoundFuncDesc<SoundService, void(ListenerType, shared_ptr<const RBX::Reflection::Tuple>)> func_setListener(&SoundService::setListener, "SetListener", "listenerType", "listener", Security::None);
Reflection::BoundFuncDesc<SoundService, shared_ptr<const RBX::Reflection::Tuple>()> func_getListener(&SoundService::getListener, "GetListener", Security::None);
SoundService::SoundService()
:dopplerscale(1.0f)
,distancefactor(10.0f)
,rolloffscale(1.0f)
,ambientReverb(NoReverb)
,channelMaster(NULL)
,currentListenerType(CameraListener)
,masterChannelFadeTimeMsec(0)
,masterChannelFadeStatus(FADE_STATUS_NONE)
,initialized(false)
,muted(false)
{
setName(sSoundService);
}
unsigned int SoundService::getfmod_version() const
{
unsigned int version;
checkResult(system->getVersion(&version), "getVersion", this, system.get());
return version;
}
void SoundService::openFmod()
{
FASTLOG1(DFLog::SoundTrace, "SoundService::openFmod(%p)", this);
if (!soundDisabled && RBX::GameSettings::singleton().soundEnabled)
{
try
{
FASTLOG(FLog::Sound, "Opening FMOD...");
FMOD::System *sys = NULL;
checkResult(FMOD::System_Create(&sys), "System_Create", this, NULL); // Create the main system object.
system.reset(sys, FMODSystemRelease());
unsigned int version;
checkResult(system->getVersion(&version), "getVersion", this, system.get());
if (version < FMOD_VERSION)
{
FASTLOG(FLog::Error, "Incorrect FMODEX DLL version");
throw std::runtime_error("Incorrect FMODEX DLL version");
}
int driver = -1;
int num_drivers = 0;
checkResultNoThrow(system->getNumDrivers(&num_drivers), "getNumDrivers", this, system.get());
if (num_drivers)
{
checkResultNoThrow(system->setDriver(0), "setDriver", this, system.get());
checkResultNoThrow(system->getDriver(&driver), "getDriver", this, system.get());
if (driver>=0)
{
checkResultNoThrow(system->getDriverInfo(driver, RBX::RbxDbgInfo::s_instance.AudioDeviceName, DBG_STRING_MAX, NULL, NULL, NULL, NULL), "getDriverInfo", this, system.get());
}
}
else
{
FASTLOG(FLog::Warning, "No active sound driver detected");
StandardOut::singleton()->print(MESSAGE_WARNING, "No active sound driver detected");
throw std::runtime_error("No active sound driver detected");
}
FMOD_INITFLAGS flags = FMOD_INIT_NORMAL;
if (DebugSettings::singleton().fmodProfiling)
flags |= FMOD_INIT_PROFILE_ENABLE ;
// lets force FMOD to work in stereo, so video recording won't be confused about more than 2 audio channels
// we do capture only left and right channels of audio into video file if you have 5.1 sound some of the sound could be played
// only on center channel, so video will miss them
// In this special case we want to use stereo output and not worry about varying matrix sizes depending on user speaker mode.
checkResult(system->setSoftwareFormat(48000, FMOD_SPEAKERMODE_DEFAULT, 0), "setSoftwareFormat", this, system.get());
// WARNING 100 - is number of simultaneous channels played by fmod and has nothing todo with output channels of sound card
checkResult(system->init(FInt::FMODSoundChannels, flags, 0), "init", this, system.get());
checkResult(system->createChannelGroup(NULL, &channelMaster), "createChannelGroup", this, system.get());
channelMaster->setVolume(RBX::GameBasicSettings::singleton().getMasterVolume());
// listen for master volume changes
gameSettingsChangedConnection = RBX::GameBasicSettings::singleton().propertyChangedSignal.connect(boost::bind(&SoundService::gameSettingsChanged,this,_1));
update3DSettings();
if (ambientReverb!=NoReverb)
updateAmbientReverb();
initialized = true;
FASTLOG(FLog::Sound, "FMOD initialization complete!");
}
catch (const RBX::base_exception&)
{
initialized = false;
// Failed to initialize sound system
if (system)
{
system.reset();
}
}
}
}
FMOD::DSP* SoundService::createDSP(FMOD_DSP_DESCRIPTION &dspdesc)
{
FASTLOG1(DFLog::SoundTrace, "SoundService::createDSP(%p)", this);
FMOD::DSP* dsp = NULL;
try
{
StandardOut::singleton()->printf(MESSAGE_INFO, "SoundService::createDSP: system value = %p, enabled value = %d", system.get(), (int)enabled());
FMOD::ChannelGroup *masterChannel = 0;
checkResult(system->getMasterChannelGroup(&masterChannel), "getMasterChannelGroup", this, system.get());
checkResult(system->createDSP(&dspdesc, &dsp), "createDSP", this, system.get());
checkResult(dsp->setBypass(false), "setBypass", this, dsp);
checkResult(masterChannel->addDSP(0, dsp), "addDSP", this, system.get());
}
catch (const RBX::base_exception& ex)
{
StandardOut::singleton()->printf(MESSAGE_ERROR, "SoundService::createDSP: %s", ex.what());
if (dsp)
{
dsp->release();
}
dsp = NULL;
}
return dsp;
}
int SoundService::getSampleRate()
{
FASTLOG1(DFLog::SoundTrace, "SoundService::getSampleRate(%p)", this);
try
{
StandardOut::singleton()->printf(MESSAGE_INFO, "SoundService::getSampleRate: system value = %p, enabled value = %d", system.get(), (int)enabled());
int samplerate;
FMOD_SPEAKERMODE speakermode;
int numrawspeakers;
checkResult(system->getSoftwareFormat(&samplerate, &speakermode, &numrawspeakers), "getSoftwareFormat", this, system.get());
StandardOut::singleton()->printf(MESSAGE_INFO, "SoundService::getSampleRate: result = %d", samplerate);
return samplerate;
}
catch (const RBX::base_exception& ex)
{
StandardOut::singleton()->printf(MESSAGE_ERROR, "SoundService::getSampleRate: %s", ex.what());
return -1;
}
}
SoundService::~SoundService()
{
RBXASSERT(stockSounds.size()==0);
RBXASSERT(loadedSounds.size()==0);
RBXASSERT(loaded3DSounds.size()==0);
RBXASSERT(!system);
RBXASSERT(!soundJob);
}
static void releaseSound(const std::pair<SoundId, boost::shared_ptr<Sound> >& p)
{
FASTLOGS(DFLog::SoundTrace, "releaseSound(%s)", p.first.c_str());
p.second->release(); // Let fmod clean things up
}
void SoundService::closeFmod()
{
FASTLOG1(DFLog::SoundTrace, "SoundService::closeFmod(%p)", this);
FASTLOG(FLog::Sound, "Closing Fmod.");
gameSettingsChangedConnection.disconnect();
stockSounds.clear();
this->removeAllChildren();
std::for_each(loadedSounds.begin(), loadedSounds.end(), releaseSound);
std::for_each(loaded3DSounds.begin(), loaded3DSounds.end(), releaseSound);
loadedSounds.clear();
loaded3DSounds.clear();
if (system)
{
system.reset();
}
initialized = false;
FASTLOG(FLog::Sound, "Fmod Closed.");
}
static FMOD_REVERB_PROPERTIES reverbs[UnderWater+1];
static void initReverbs()
{
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_OFF; reverbs[NoReverb] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_GENERIC; reverbs[GenericReverb] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_PADDEDCELL; reverbs[PaddedCell] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_PADDEDCELL; reverbs[Room] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_BATHROOM; reverbs[Bathroom] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_LIVINGROOM; reverbs[LivingRoom] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_STONEROOM; reverbs[StoneRoom] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_AUDITORIUM; reverbs[Auditorium] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_CONCERTHALL; reverbs[ConcertHall] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_CAVE; reverbs[Cave] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_ARENA; reverbs[Arena] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_HANGAR; reverbs[Hangar] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_CARPETTEDHALLWAY; reverbs[CarpettedHallway] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_HALLWAY; reverbs[Hallway] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_STONECORRIDOR; reverbs[StoneCorridor] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_ALLEY; reverbs[Alley] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_FOREST; reverbs[Forest] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_CITY; reverbs[City] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_MOUNTAINS; reverbs[Mountains] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_QUARRY; reverbs[Quarry] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_PLAIN; reverbs[Plain] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_PARKINGLOT; reverbs[ParkingLot] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_SEWERPIPE; reverbs[SewerPipe] = p; }
{ FMOD_REVERB_PROPERTIES p = FMOD_PRESET_UNDERWATER; reverbs[UnderWater] = p; }
}
void SoundService::updateAmbientReverb()
{
FASTLOG1(DFLog::SoundTrace, "SoundService::updateAmbientReverb(%p)", this);
if (!system)
return;
static boost::once_flag flag = BOOST_ONCE_INIT;
boost::call_once(&initReverbs, flag);
checkResultNoThrow(system->setReverbProperties(0, &reverbs[ambientReverb]), "setReverbProperties", this, system.get());
}
void SoundService::update3DSettings()
{
FASTLOG1(DFLog::SoundTrace, "SoundService::update3DSettings(%p)", this);
if (system)
checkResultNoThrow(system->set3DSettings(dopplerscale, distancefactor, rolloffscale), "set3DSettings", this, system.get());
}
void SoundService::setAmbientReverb(const ReverbType& value)
{
FASTLOG1(DFLog::SoundTrace, "SoundService::setAmbientReverb(%p)", this);
if (value!=ambientReverb)
{
ambientReverb = value;
raisePropertyChanged(prop_AmbientReverb);
updateAmbientReverb();
}
}
void SoundService::setListener(ListenerType listenerType, shared_ptr<const RBX::Reflection::Tuple> value)
{
if (!((value && value->values.size() > 0) || listenerType == CameraListener))
{
throw RBX::runtime_error("SoundService:SetListener called with an incorrect argument.");
return;
}
if (listenerType == CFrame)
{
Reflection::ValueArray valueArray = value->values;
Reflection::ValueArray::iterator tupleIter = valueArray.begin();
if (tupleIter->isType<CoordinateFrame>())
{
currentListenerValues.listenCFrame = tupleIter->cast<CoordinateFrame>();
}
else
{
throw RBX::runtime_error("SoundService:SetListener value given is not a valid CFrame when given Enum.ListenerType.CFrame");
return;
}
}
else if (listenerType == ObjectPosition || listenerType == ObjectCFrame)
{
Reflection::ValueArray valueArray = value->values;
Reflection::ValueArray::iterator tupleIter = valueArray.begin();
if (tupleIter->isType<shared_ptr<Instance> >())
{
shared_ptr<Instance> instance = tupleIter->cast<shared_ptr<Instance> >();
if (shared_ptr<IHasLocation> location = shared_dynamic_cast<IHasLocation>(instance))
{
currentListenerValues.listenObject = location;
}
else
{
throw RBX::runtime_error("SoundService:SetListener value given does not have a location");
return;
}
}
else
{
throw RBX::runtime_error("SoundService:SetListener value given is not a valid instance");
return;
}
}
currentListenerType = listenerType;
}
shared_ptr<const RBX::Reflection::Tuple> SoundService::getListener()
{
shared_ptr<RBX::Reflection::Tuple> result(new RBX::Reflection::Tuple(2));
result->values[0] = currentListenerType;
if (currentListenerType == CFrame)
{
result->values[1] = currentListenerValues.listenCFrame;
}
else if (currentListenerType == ObjectPosition || currentListenerType == ObjectCFrame)
{
result->values[1] = shared_dynamic_cast<Instance>(currentListenerValues.listenObject);
}
return result;
}
void SoundService::playSound(SoundType soundType)
{
FASTLOG1(DFLog::SoundTrace, "SoundService::playSound(%p)", this);
StockSounds::iterator iter = stockSounds.find(soundType);
if (iter!=stockSounds.end())
{
if (DFLog::SoundTrace)
{
std::stringstream ss;
ss << reinterpret_cast<void*>(iter->second.get()) << ") with soundId = " << iter->second->getSoundId().toString() << ", isLoaded = " << iter->second->isSoundLoaded();
FASTLOGS(DFLog::SoundTrace, "SoundService::playSound(%s", ss.str().c_str());
}
iter->second->play();
}
}
void SoundService::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
{
FASTLOG3(DFLog::SoundTrace, "SoundService::onServiceProvider(%p, %p, %p)", this, oldProvider, newProvider);
TaskScheduler::singleton().removeBlocking(soundJob);
soundJob.reset();
if (statsItem) {
statsItem->setParent(NULL);
statsItem.reset();
}
if (oldProvider)
closeFmod();
Super::onServiceProvider(oldProvider, newProvider);
if (newProvider)
{
if (!soundDisabled)
{
openFmod();
soundJob = shared_ptr<SoundJob>(new SoundJob(this));
TaskScheduler::singleton().add(soundJob);
}
}
Stats::StatsService* stats = ServiceProvider::create<Stats::StatsService>(newProvider);
if (stats) {
statsItem = SoundServiceStatsItem::create(this);
statsItem->setParent(stats);
}
}
bool SoundService::convert(const G3D::Vector3& src, FMOD_VECTOR& dst)
{
G3D::Vector3 filter = src;
if (Math::isDenormal(filter.x))
filter.x = 0;
if (Math::isDenormal(filter.y))
filter.y = 0;
if (Math::isDenormal(filter.z))
filter.z = 0;
if (Math::isNanInfDenormVector3(filter))
return false;
filter = filter.clamp(-100000, 100000);
dst.x = filter.x;
dst.y = filter.y;
dst.z = filter.z;
return true;
}
CoordinateFrame SoundService::getListenCFrame(Camera* camera)
{
CoordinateFrame cf = camera->getCameraCoordinateFrame();
if (currentListenerType == CFrame)
{
cf = currentListenerValues.listenCFrame;
}
else if (currentListenerType == ObjectPosition)
{
cf.translation = currentListenerValues.listenObject->getLocation().translation;
}
else if (currentListenerType == ObjectCFrame)
{
cf = currentListenerValues.listenObject->getLocation();
}
return cf;
}
void SoundService::step(const Time::Interval& timeSinceLastStep)
{
if (enabled())
{
Workspace* workspace = ServiceProvider::find<Workspace>(this);
if (workspace!=NULL)
{
Camera* camera = workspace->getCamera();
if (camera!=NULL)
{
CoordinateFrame cf = getListenCFrame(camera);
FMOD_VECTOR listener_pos;
if (convert(cf.translation, listener_pos))
{
FMOD_VECTOR listener_forward;
if (convert(-cf.lookVector(), listener_forward))
{
FMOD_VECTOR listener_up;
if (convert(cf.upVector(), listener_up))
{
FMOD_VECTOR listener_vel;
listener_vel.x = listener_vel.y = listener_vel.z = 0;
RBX::PVInstance* pv = dynamic_cast<PVInstance*>(camera->getCameraSubject());
RBX::PartInstance* part = pv ? pv->getPrimaryPart() : NULL;
if (part)
convert(part->getVelocity().linear, listener_vel);
// TODO: Get camera velocity
FMOD_RESULT fmodResult = system->set3DListenerAttributes(0, &listener_pos, &listener_vel, &listener_forward, &listener_up);
if (FMOD_OK != fmodResult) // don't overlog during traces
{
checkResultNoThrow(fmodResult, "set3DListenerAttributes", this, system.get()); // update 'ears'
}
}
}
}
}
}
garbageCollectSounds();
}
updateMasterChannelGroup(timeSinceLastStep);
updateSoundChannels(timeSinceLastStep);
if (enabled())
{
FMOD_RESULT fmodResult = system->update();
if (FMOD_OK != fmodResult) // don't overlog during traces
{
checkResultNoThrow(fmodResult, "update", this, system.get());
}
}
}
void SoundService::updateMasterChannelGroup(const Time::Interval& timeSinceLastStep)
{
if (!channelMaster || masterChannelFadeStatus == FADE_STATUS_NONE)
{
return;
}
const bool muting = (masterChannelFadeStatus == FADE_STATUS_OUT);
const float storedMasterVolume = muting ? getMasterVolume() : RBX::GameBasicSettings::singleton().getMasterVolume();
float volumeStep = 0.0f;
if (masterChannelFadeTimeMsec <= 0.0f)
{
volumeStep = getMasterVolume() * (muting ? -1.0f : 1.0f);
}
else
{
volumeStep = ( (masterChannelFadeTimeMsec/timeSinceLastStep.msec() * storedMasterVolume)/masterChannelFadeTimeMsec ) * (muting ? -1.0f : 1.0f);
}
if (!muting)
{
muteAllChannels(false);
}
if ( muting ? (getMasterVolume() > 0.0f) : (getMasterVolume() < storedMasterVolume))
{
float newMasterVolume = getMasterVolume() + volumeStep;
if (fabs(newMasterVolume) <= 0.01f)
newMasterVolume = 0.0f;
else if(newMasterVolume >= 0.99f)
newMasterVolume = 1.0f;
setMasterVolume(newMasterVolume);
}
if (getMasterVolume() <= 0.0f && muting)
{
muteAllChannels(true);
masterChannelFadeStatus = FADE_STATUS_NONE;
}
else if (!muting && getMasterVolume() >= storedMasterVolume)
{
setMasterVolume(storedMasterVolume);
masterChannelFadeStatus = FADE_STATUS_NONE;
}
}
void SoundService::updateSoundChannels(const Time::Interval& timeSinceLastStep)
{
FASTLOG1(DFLog::SoundTiming, "Number of channels = %d", soundChannels.size());
for (SoundChannels::iterator it = soundChannels.begin(); soundChannels.end() != it; ++it)
{
SoundChannel *soundChannel = *it;
soundChannel->updateListenState(timeSinceLastStep);
}
}
void SoundService::getCpuStats(CpuStats& stats) const
{
if (!system)
{
stats.dsp = 0;
stats.stream = 0;
stats.geometry = 0;
stats.update = 0;
stats.total = 0;
return;
}
CpuStats stats100;
system->getCPUUsage(&stats100.dsp, &stats100.stream, &stats100.geometry, &stats100.update, &stats100.total);
stats.dsp = 0.01 * stats100.dsp;
stats.geometry = 0.01 * stats100.geometry;
stats.stream = 0.01 * stats100.stream;
stats.total = 0.01 * stats100.total;
stats.update = 0.01 * stats100.update;
}
void SoundService::getSoundStats(const LoadedSounds& sounds, unsigned int& numSounds, unsigned int& numUnusedSounds)
{
for (LoadedSounds::const_iterator iter = sounds.begin(); iter!=sounds.end(); ++iter)
{
++numSounds;
if (!iter->second->isReferenced())
++numUnusedSounds;
}
}
void SoundService::getSoundStats(unsigned int& numSounds, unsigned int& numUnusedSounds) const
{
numSounds = numUnusedSounds = 0;
getSoundStats(loadedSounds, numSounds, numUnusedSounds);
getSoundStats(loaded3DSounds, numSounds, numUnusedSounds);
}
void SoundService::getChannelsPlaying(int& value) const
{
FASTLOG1(DFLog::SoundTrace, "SoundService::getChannelsPlaying(%p)", this);
value = 0;
if (system)
checkResultNoThrow(system->getChannelsPlaying(&value), "getChannelIsPlaying", this, system.get());
}
bool SoundService::isMuted()
{
if (channelMaster)
{
bool isMuted = false;
FMOD::System* systemCheck = NULL;
FMOD_RESULT result = channelMaster->getSystemObject(&systemCheck);
if (result == FMOD_OK && systemCheck)
{
channelMaster->getMute(&isMuted);
}
return isMuted;
}
else
{
return false;
}
}
void SoundService::muteAllChannels(bool mute)
{
FASTLOG1(DFLog::SoundTrace, "SoundService::muteAllChannels(%p)", this);
if (channelMaster)
{
FMOD::System* systemCheck = NULL;
FMOD_RESULT result = channelMaster->getSystemObject(&systemCheck);
if (result == FMOD_OK && systemCheck)
{
checkResult(channelMaster->setMute(mute), "setMute", this, channelMaster);
}
}
}
void SoundService::gameSettingsChanged(const Reflection::PropertyDescriptor* propertyDescriptor)
{
if (propertyDescriptor == &RBX::GameBasicSettings::singleton().prop_masterVolume)
{
setMasterVolume(RBX::GameBasicSettings::singleton().getMasterVolume());
}
}
void SoundService::setMasterVolume(float value)
{
if (channelMaster)
{
bool muted = false;
channelMaster->getMute(&muted);
if (!muted)
{
channelMaster->setVolume(value);
}
}
}
float SoundService::getMasterVolume()
{
float volume = -1;
if (channelMaster)
{
channelMaster->getVolume(&volume);
}
return volume;
}
void SoundService::setMasterVolumeFadeIn(float timeToFadeMsec)
{
masterChannelFadeTimeMsec = G3D::clamp(timeToFadeMsec, 0.0f, 100000.0f);
masterChannelFadeStatus = FADE_STATUS_IN;
}
void SoundService::setMasterVolumeFadeOut(float timeToFadeMsec)
{
masterChannelFadeTimeMsec = G3D::clamp(timeToFadeMsec, 0.0f, 100000.0f);
masterChannelFadeStatus = FADE_STATUS_OUT;
}
void SoundService::gcSounds(LoadedSounds& sounds)
{
for (LoadedSounds::iterator iter = sounds.begin(); iter!=sounds.end();)
{
if (!iter->second->isReferenced() && G3D::uniformRandom()>=decay)
{
FASTLOGS(DFLog::SoundTrace, "Garbage collecting soundId = %s", iter->second->id.c_str());
iter->second->release();
sounds.erase(iter++);
}
else
{
++iter;
}
}
}
void SoundService::garbageCollectSounds()
{
if (nextGarbageCollectTime > Time::now<Time::Fast>())
return;
// Now flush the loadedSounds database of empty items
gcSounds(loadedSounds);
gcSounds(loaded3DSounds);
nextGarbageCollectTime = Time::now<Time::Fast>() + gcTime;
}
void SoundService::registerSoundChannel(SoundChannel *soundChannel)
{
if (FLog::Sound)
{
std::stringstream ss;
ss << "soundChannel = " << reinterpret_cast<void*>(soundChannel) << ", soundId = " << soundChannel->getSoundId().c_str();
FASTLOGS(FLog::Sound, "Registering with SoundService: %s", ss.str().c_str());
}
soundChannels.insert(soundChannel);
}
void SoundService::unregisterSoundChannel(SoundChannel *soundChannel)
{
SoundChannels::iterator iter = soundChannels.find(soundChannel);
if (soundChannels.end() != iter)
{
if (FLog::Sound)
{
SoundChannel *soundChannel = *iter;
std::stringstream ss;
ss << "soundChannel = " << reinterpret_cast<void*>(soundChannel) << ", soundId = " << soundChannel->getSoundId().c_str();
FASTLOGS(FLog::Sound, "Unregistering from SoundService: %s", ss.str().c_str());
}
soundChannels.erase(iter);
}
}
shared_ptr<Sound> SoundService::loadSound(SoundId id, bool is3D)
{
LoadedSounds& database(is3D ? loaded3DSounds : loadedSounds);
LoadedSounds::iterator iter = database.find(id);
// don't return this sound object if we are streaming, each stream needs a new object
if (iter!=database.end() && !iter->second->getIsStreaming())
{
return iter->second;
}
shared_ptr<Sound> result(new Sound(system, id, is3D));
database[id] = result;
return result;
}
void SoundService::checkResultNoThrow(FMOD_RESULT result, const char* fmodOperation, const void *rbxFmodParent, const void *fmodObject)
{
if (FMOD_OK != result)
{
if (DebugSettings::singleton().soundWarnings || FLog::FMOD)
{
char err[512];
strncpy(err, FMOD_ErrorString(result), 512);
if (DebugSettings::singleton().soundWarnings)
{
StandardOut::singleton()->printf(MESSAGE_WARNING, "FMOD %d: %s during %s", result, err, fmodOperation);
}
writeFMODLog(err, fmodOperation, rbxFmodParent, fmodObject);
RBXASSERT(!FFlag::DebugBreakOnFMODErrors || FMOD_OK == result);
}
}
}
void SoundService::checkResult(FMOD_RESULT result, const char* fmodOperation, const void *rbxFmodParent, const void *fmodObject)
{
if (FMOD_OK != result)
{
std::string message = RBX::format("FMOD %d: %s during %s", result, FMOD_ErrorString(result), fmodOperation);
StandardOut::singleton()->print(MESSAGE_WARNING, message.c_str());
writeFMODLog(FMOD_ErrorString(result), fmodOperation, rbxFmodParent, fmodObject);
if (FMOD_OK != result)
{
throw std::runtime_error(message);
}
}
}