mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-05 13:17:49 +00:00
1222 lines
37 KiB
C++
1222 lines
37 KiB
C++
/* Copyright 2003-2014 ROBLOX Corporation, All Rights Reserved */
|
|
#include "stdafx.h"
|
|
|
|
#include "Util/SoundChannel.h"
|
|
#include "V8DataModel/ContentProvider.h"
|
|
#include "Util/SoundService.h"
|
|
|
|
#include "V8DataModel/Workspace.h"
|
|
#include "V8DataModel/PartInstance.h"
|
|
|
|
#include "V8DataModel/GameSettings.h"
|
|
#include "V8DataModel/PlayerGui.h"
|
|
#include "Network/Players.h"
|
|
|
|
#include "FastLog.h"
|
|
#include "rbx/RbxDbgInfo.h"
|
|
|
|
#define FMOD_DEFAULT_CHANNEL_PRIORITY 128
|
|
#define FMOD_LONG_SOUND_CHANNEL_PRIORITY FMOD_DEFAULT_CHANNEL_PRIORITY - 16
|
|
|
|
|
|
LOGGROUP(FMOD)
|
|
LOGGROUP(Sound)
|
|
DYNAMIC_LOGGROUP(SoundTiming)
|
|
DYNAMIC_LOGGROUP(SoundTrace)
|
|
|
|
FASTINTVARIABLE(MinMsecBetweenTimePosEventReplication, 100)
|
|
FASTINTVARIABLE(MinSecondLengthForLongSoundChannel, 5)
|
|
FASTSTRINGVARIABLE(AssetTypeHeaderForSounds, "")
|
|
DYNAMIC_FASTFLAGVARIABLE(SoundFailedToLoadContext, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(MinMaxDistanceEnabled, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(RollOffModeEnabled, false)
|
|
|
|
namespace RBX
|
|
{
|
|
|
|
namespace Reflection
|
|
{
|
|
template<>
|
|
EnumDesc<RBX::Soundscape::RollOffMode>::EnumDesc()
|
|
:EnumDescriptor("RollOffMode")
|
|
{
|
|
addPair(RBX::Soundscape::Inverse, "Inverse");
|
|
addPair(RBX::Soundscape::Linear, "Linear");
|
|
}
|
|
template<>
|
|
RBX::Soundscape::RollOffMode& Variant::convert<RBX::Soundscape::RollOffMode>(void)
|
|
{
|
|
return genericConvert<RBX::Soundscape::RollOffMode>();
|
|
}
|
|
} //namespace Reflection
|
|
template<>
|
|
bool StringConverter<RBX::Soundscape::RollOffMode>::convertToValue(const std::string& text, RBX::Soundscape::RollOffMode& value)
|
|
{
|
|
return Reflection::EnumDesc<RBX::Soundscape::RollOffMode>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
}
|
|
|
|
namespace RBX
|
|
{
|
|
|
|
void registerSound()
|
|
{
|
|
Soundscape::SoundChannel::classDescriptor();
|
|
}
|
|
|
|
namespace Soundscape
|
|
{
|
|
REFLECTION_BEGIN();
|
|
static Reflection::PropDescriptor<SoundChannel, SoundId> sound_desc_SoundId("SoundId", category_Data, &SoundChannel::getSoundId, &SoundChannel::setSoundId);
|
|
static Reflection::PropDescriptor<SoundChannel, float> sound_desc_Volume("Volume", category_Data, &SoundChannel::getVolume, &SoundChannel::setVolume);
|
|
static Reflection::PropDescriptor<SoundChannel, float> sound_desc_Pitch("Pitch", category_Data, &SoundChannel::getPitch, &SoundChannel::setPitch);
|
|
static Reflection::PropDescriptor<SoundChannel, float> sound_desc_MinDistance("MinDistance", category_Data, &SoundChannel::getMinDistance, &SoundChannel::setMinDistance);
|
|
static Reflection::PropDescriptor<SoundChannel, float> sound_desc_MaxDistance("MaxDistance", category_Data, &SoundChannel::getMaxDistance, &SoundChannel::setMaxDistance);
|
|
// DFFLag::RollOffModeEnabled
|
|
// static Reflection::EnumPropDescriptor<SoundChannel, RollOffMode> sound_desc_RollOffMode("RollOffMode", category_Data, &SoundChannel::getRollOffMode, &SoundChannel::setRollOffMode);
|
|
|
|
static Reflection::PropDescriptor<SoundChannel, double> sound_desc_SoundLength("TimeLength", category_Data, &SoundChannel::getSoundLength, NULL, Reflection::PropertyDescriptor::UI);
|
|
static Reflection::PropDescriptor<SoundChannel, double> sound_desc_SoundPosition("TimePosition", category_Data, &SoundChannel::getSoundPosition, &SoundChannel::setSoundPositionLua, Reflection::PropertyDescriptor::UI);
|
|
|
|
static Reflection::PropDescriptor<SoundChannel, bool> sound_desc_Looped("Looped", category_Behavior, &SoundChannel::getLooped, &SoundChannel::setLooped);
|
|
Reflection::BoundProp< bool> SoundChannel::sound_desc_playOnRemove("PlayOnRemove", category_Behavior, &SoundChannel::playOnRemove);
|
|
|
|
static Reflection::BoundFuncDesc<SoundChannel, void()> sound_playFunction(&SoundChannel::play, "Play", Security::None);
|
|
static Reflection::BoundFuncDesc<SoundChannel, void()> sound_resumeFunction(&SoundChannel::resume, "Resume", Security::None);
|
|
static Reflection::BoundFuncDesc<SoundChannel, void()> sound_pauseFunction(&SoundChannel::pause, "Pause", Security::None);
|
|
static Reflection::BoundFuncDesc<SoundChannel, void()> sound_stopFunction(&SoundChannel::stop, "Stop", Security::None);
|
|
|
|
static Reflection::PropDescriptor<SoundChannel, bool> sound_desc_IsPlaying("IsPlaying", category_Data, &SoundChannel::isPlaying, NULL, Reflection::PropertyDescriptor::UI);
|
|
static Reflection::PropDescriptor<SoundChannel, bool> sound_desc_isPaused("IsPaused", category_Data, &SoundChannel::isPaused, NULL, Reflection::PropertyDescriptor::UI);
|
|
|
|
static Reflection::EventDesc<SoundChannel, void(std::string, int)> sound_loopedSignal(&SoundChannel::soundLoopedSignal, "DidLoop", "soundId", "numOfTimesLooped");
|
|
static Reflection::EventDesc<SoundChannel, void(std::string)> sound_pausedSignal(&SoundChannel::soundPausedSignal, "Paused", "soundId");
|
|
static Reflection::EventDesc<SoundChannel, void(std::string)> sound_playedSignal(&SoundChannel::soundPlayedSignal, "Played", "soundId");
|
|
static Reflection::EventDesc<SoundChannel, void(std::string)> sound_stoppedSignal(&SoundChannel::soundStoppedSignal, "Stopped", "soundId");
|
|
static Reflection::EventDesc<SoundChannel, void(std::string)> sound_endedSignal(&SoundChannel::soundEndedSignal, "Ended", "soundId");
|
|
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Backend Events/Properties
|
|
/////////////////////////////////////////////////
|
|
static Reflection::PropDescriptor<SoundChannel, int> sound_desc_PlayCount("PlayCount", category_Data, &SoundChannel::getPlayCount, &SoundChannel::setPlayCount, Reflection::PropertyDescriptor::REPLICATE_ONLY);
|
|
static Reflection::RemoteEventDesc<SoundChannel, void(int)> event_timePositionUpdatedFromServer(&SoundChannel::timePositionUpdatedFromServerSignal, "TimePositionUpdated", "newPositionSeconds", Security::Roblox, Reflection::RemoteEventCommon::REPLICATE_ONLY, Reflection::RemoteEventCommon::BROADCAST);
|
|
static Reflection::RemoteEventDesc<SoundChannel, void(int)> event_timePositionUpdatedFromServerScript(&SoundChannel::timePositionUpdatedFromServerScriptSignal, "TimePositionUpdatedFromScript", "newPositionSeconds", Security::Roblox, Reflection::RemoteEventCommon::REPLICATE_ONLY, Reflection::RemoteEventCommon::BROADCAST);
|
|
static Reflection::RemoteEventDesc<SoundChannel, void(int)> event_soundResumedFromServer(&SoundChannel::soundResumedFromServerSignal, "SoundResumedFromServer", "currentTimePosition", Security::Roblox, Reflection::RemoteEventCommon::REPLICATE_ONLY, Reflection::RemoteEventCommon::BROADCAST);
|
|
|
|
|
|
//////////////////////////////////////////////////////
|
|
// DEPRECATED LUA FUNCTIONS/PROPS
|
|
/////////////////////////////////////////////////
|
|
static Reflection::BoundFuncDesc<SoundChannel, void()> sound_stopFunctionDep(&SoundChannel::stop, "stop", Security::None, Reflection::Descriptor::Attributes::deprecated(sound_stopFunction));
|
|
static Reflection::BoundFuncDesc<SoundChannel, void()> sound_playFunctionDeprecated(&SoundChannel::play, "play", Security::None, Reflection::Descriptor::Attributes::deprecated(sound_playFunction));
|
|
static Reflection::BoundFuncDesc<SoundChannel, void()> sound_dep_pauseFunction(&SoundChannel::pause, "pause", Security::None, Reflection::Descriptor::Attributes::deprecated(sound_pauseFunction));
|
|
static Reflection::PropDescriptor<SoundChannel, bool> sound_desc_dep_IsPlaying("isPlaying", category_Data, &SoundChannel::isPlaying, NULL, Reflection::PropertyDescriptor::Attributes::deprecated(sound_desc_IsPlaying));
|
|
REFLECTION_END();
|
|
|
|
SoundChannel::SoundChannel()
|
|
:looped(false)
|
|
,is3D(false)
|
|
,playOnRemove(false)
|
|
,fmod_channel(NULL)
|
|
,part(NULL)
|
|
,volume(0.5)
|
|
,pitch(1)
|
|
,minDistance(10)
|
|
,maxDistance(100000)
|
|
,rollOff(Inverse)
|
|
,soundDisabled(false)
|
|
,playCount(-1)
|
|
,reqPlayCount(-1)
|
|
,soundPositionSeconds(0)
|
|
,invalidChannel(true)
|
|
,numOfTimesLooped(0)
|
|
,lastSoundPositionMsec(0)
|
|
{
|
|
this->setName("Sound");
|
|
}
|
|
|
|
SoundChannel::~SoundChannel()
|
|
{
|
|
FASTLOG3(DFLog::SoundTrace, "SoundChannel::~SoundChannel(%p, %p, %p)", this, fmod_channel, sound.get());
|
|
releaseChannel();
|
|
RBXASSERT(fmod_channel==NULL);
|
|
RBXASSERT(!sound);
|
|
}
|
|
|
|
|
|
void SoundChannel::releaseChannel()
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::releaseChannel(%p, %p)", this, fmod_channel);
|
|
if (fmod_channel)
|
|
{
|
|
#ifdef _DEBUG
|
|
SoundChannel* oldSound;
|
|
SoundService::checkResultNoThrow(fmod_channel->getUserData(reinterpret_cast<void**>(&oldSound)), "getUserData", this, fmod_channel);
|
|
RBXASSERT(oldSound==0 || oldSound==this);
|
|
#endif
|
|
SoundService::checkResultNoThrow(fmod_channel->setUserData(reinterpret_cast<void*>(NULL)), "setUserData release", this, fmod_channel);
|
|
fmod_channel = NULL;
|
|
}
|
|
if (sound)
|
|
{
|
|
sound.reset();
|
|
}
|
|
}
|
|
|
|
bool SoundChannel::askSetParent(const Instance* instance) const
|
|
{
|
|
// Sounds can be a child of anything
|
|
return true;
|
|
}
|
|
|
|
|
|
void SoundChannel::updateListenState(const Time::Interval& timeSinceLastStep)
|
|
{
|
|
if (fmod_channel)
|
|
{
|
|
update3D(fmod_channel);
|
|
}
|
|
|
|
if (controlledByAndIsServer() && (timeSinceLastStep.msec() > 0))
|
|
{
|
|
// we aren't actually playing sounds, but should keep the scrub in a good location
|
|
// for client replication (say we start playing a sound in workspace when it is actually half over)
|
|
|
|
double position = soundPositionSeconds;
|
|
if (isPlaying() && !isPaused())
|
|
{
|
|
position += (timeSinceLastStep.seconds() * getPitch());
|
|
double soundLength = getSoundLength();
|
|
|
|
if (!getLooped() && (position > soundLength) && sound)
|
|
{
|
|
position = 0;
|
|
DataModel::scoped_write_request request(RBX::DataModel::get(this));
|
|
stop();
|
|
}
|
|
else
|
|
{
|
|
if ( getLooped() && isPlaying() && (position > soundLength) )
|
|
{
|
|
numOfTimesLooped++;
|
|
|
|
DataModel::scoped_write_request request(RBX::DataModel::get(this));
|
|
soundLoopedSignal(getSoundId().toString(), numOfTimesLooped);
|
|
}
|
|
|
|
position = fmod(position, (soundLength ? soundLength : 1));
|
|
DataModel::scoped_write_request request(RBX::DataModel::get(this));
|
|
setSoundPosition(position);
|
|
}
|
|
}
|
|
}
|
|
else if (!controlledByAndIsServer() && fmod_channel && getLooped() && isPlaying())
|
|
{
|
|
unsigned currentPosMs = 0;
|
|
fmod_channel->getPosition(¤tPosMs,FMOD_TIMEUNIT_MS);
|
|
|
|
if (currentPosMs < lastSoundPositionMsec)
|
|
{
|
|
numOfTimesLooped++;
|
|
|
|
DataModel::scoped_write_request request(RBX::DataModel::get(this));
|
|
soundLoopedSignal(getSoundId().toString(), numOfTimesLooped);
|
|
}
|
|
|
|
lastSoundPositionMsec = currentPosMs;
|
|
}
|
|
}
|
|
|
|
void SoundChannel::onAncestorChanged(const AncestorChanged& event)
|
|
{
|
|
FASTLOG3(DFLog::SoundTrace, "SoundChannel::onAncestorChanged(%p, %p), event.child = %p", this, event.newParent, event.child);
|
|
Super::onAncestorChanged(event);
|
|
|
|
if (event.child==this)
|
|
{
|
|
part = Instance::fastDynamicCast<PartInstance>(event.newParent);
|
|
}
|
|
|
|
if (playOnRemove && !getLooped())
|
|
{
|
|
// If this Sound or one of its parents is being removed from the Workspace, then play the sound!
|
|
const Instance* oldWorkspace = ServiceProvider::find<Workspace>(event.oldParent);
|
|
const bool wasInWorkspace = oldWorkspace && (event.oldParent==oldWorkspace || event.oldParent->isDescendantOf(oldWorkspace));
|
|
|
|
if (wasInWorkspace)
|
|
{
|
|
const Instance* newWorkspace = ServiceProvider::find<Workspace>(event.newParent);
|
|
const bool isInWorkspace = newWorkspace && (event.newParent==newWorkspace || event.newParent->isDescendantOf(newWorkspace));
|
|
if (!isInWorkspace)
|
|
{
|
|
FASTLOG1(DFLog::SoundTrace, "Play on remove with SoundChannel %p", this);
|
|
RBXASSERT(oldWorkspace);
|
|
loadSound(oldWorkspace, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundChannel::serverUpdatedTimePositionFromScript(unsigned int timePosition)
|
|
{
|
|
// a server script updated time position, make sure we update too
|
|
setSoundPosition(timePosition);
|
|
}
|
|
|
|
void SoundChannel::serverUpdatedTimePosition(unsigned int timePosition)
|
|
{
|
|
// only get initial position from server, then don't worry about this type of update
|
|
serverUpdatedTimeConnection.disconnect();
|
|
setSoundPosition(timePosition);
|
|
}
|
|
|
|
void SoundChannel::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
|
|
{
|
|
FASTLOG5(DFLog::SoundTrace, "SoundChannel::onServiceProvider(%p, %p, %p, %d, %d)", this, oldProvider, newProvider, playCount, reqPlayCount);
|
|
if (oldProvider)
|
|
{
|
|
// Looped sounds should be turned off when we leave the world (like a rocket engine)
|
|
// Non-looped sounds can finish playing (like an explosion)
|
|
if (getLooped())
|
|
{
|
|
FASTLOG1(DFLog::SoundTrace, "SoundChannel::onServiceProvider(%p) calling stop on looped sound.", this);
|
|
stop();
|
|
}
|
|
|
|
releaseChannel();
|
|
SoundService* soundService = ServiceProvider::create<SoundService>(oldProvider);
|
|
if (soundService)
|
|
{
|
|
soundService->unregisterSoundChannel(this);
|
|
}
|
|
|
|
serverUpdatedTimeConnection.disconnect();
|
|
serverScriptUpdatedTimeConnection.disconnect();
|
|
serverResumedSoundConnection.disconnect();
|
|
}
|
|
|
|
Super::onServiceProvider(oldProvider,newProvider);
|
|
|
|
if (newProvider)
|
|
{
|
|
SoundService* soundService = ServiceProvider::create<SoundService>(newProvider);
|
|
soundService->registerSoundChannel(this);
|
|
part = Instance::fastDynamicCast<PartInstance>(this->getParent());
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::onServiceProvider(%p) setting part to %p", this, part);
|
|
if (soundService && soundService->enabled())
|
|
{
|
|
soundDisabled = false; // Worth trying again to see if we have a working sound manager
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::onServiceProvider(%p), playCount < reqPlayCount = %d", this, playCount < reqPlayCount);
|
|
loadSound(this, playCount < reqPlayCount);
|
|
playCount = reqPlayCount;
|
|
}
|
|
|
|
lastTimePosReplication.reset();
|
|
|
|
if (RBX::Network::Players::clientIsPresent(newProvider))
|
|
{
|
|
// only get initial position update from server if sound is replicated to each client
|
|
if (isHeardGlobally())
|
|
{
|
|
serverUpdatedTimeConnection = timePositionUpdatedFromServerSignal.connect(boost::bind(&SoundChannel::serverUpdatedTimePosition, this, _1));
|
|
}
|
|
|
|
serverScriptUpdatedTimeConnection = timePositionUpdatedFromServerScriptSignal.connect(boost::bind(&SoundChannel::serverUpdatedTimePositionFromScript, this, _1));
|
|
serverResumedSoundConnection = soundResumedFromServerSignal.connect(boost::bind(&SoundChannel::resume, this));
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
void onSoundLoadedForSoundChannel(shared_ptr<SoundChannel> soundChannel, const SoundId &soundId, const Instance *context, bool shouldPlayOnLoad, AsyncHttpQueue::RequestResult result, shared_ptr<const std::string> item)
|
|
{
|
|
switch (result)
|
|
{
|
|
case AsyncHttpQueue::Failed:
|
|
FASTLOGS(FLog::Sound, "onSoundLoaded Failed to load %s", soundId.c_str());
|
|
if (DFFlag::SoundFailedToLoadContext && context && !soundId.isNull())
|
|
{
|
|
RBX::StandardOut::singleton()->printf(MESSAGE_ERROR, "Sound failed to load %s : %s", context->getFullName().c_str(), soundId.c_str());
|
|
}
|
|
break;
|
|
case AsyncHttpQueue::Waiting:
|
|
FASTLOGS(FLog::Sound, "onSoundLoaded Waiting for %s", soundId.c_str());
|
|
break;
|
|
case AsyncHttpQueue::Succeeded:
|
|
FASTLOGS(FLog::Sound, "onSoundLoaded Succeeded in loading %s", soundId.c_str());
|
|
|
|
// Make sure we only trigger an onSoundLoaded event if our callback contained the data we want
|
|
// and we don't currently have the sound loaded.
|
|
if (soundChannel->getSoundId() == soundId)
|
|
{
|
|
soundChannel->onSoundLoaded(context, shouldPlayOnLoad);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
void SoundChannel::onSoundLoaded(const Instance *context, bool shouldPlayOnLoad)
|
|
{
|
|
FASTLOG3(DFLog::SoundTrace, "SoundChannel::onSoundLoaded(%p, %p, %d)", this, context, shouldPlayOnLoad);
|
|
|
|
is3D = part != NULL;
|
|
|
|
SoundService* soundService = ServiceProvider::create<SoundService>(context);
|
|
if (soundService)
|
|
{
|
|
sound = soundService->loadSound(getSoundId(), is3D);
|
|
|
|
raisePropertyChanged(sound_desc_SoundLength);
|
|
|
|
// Only play our sound at the appropriate time.
|
|
if (shouldPlayOnLoad)
|
|
{
|
|
playLocal(context);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SoundChannel::isPaused() const
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::isPaused(%p, %p)", this, fmod_channel);
|
|
|
|
if (!fmod_channel)
|
|
{
|
|
return playCount <= 0;
|
|
}
|
|
|
|
bool paused = true;
|
|
if (!invalidChannel)
|
|
{
|
|
FMOD_RESULT fmodResult = fmod_channel->getPaused(&paused);
|
|
if (FMOD_OK != fmodResult)
|
|
{
|
|
invalidChannel = true;
|
|
SoundService::checkResultNoThrow(fmodResult, "getPaused", this, fmod_channel);
|
|
paused = true;
|
|
}
|
|
FASTLOG2(DFLog::SoundTrace, "Returning SoundChannel::isPaused(%p) = %d", this, paused);
|
|
}
|
|
|
|
return paused;
|
|
}
|
|
|
|
bool SoundChannel::isPlaying() const
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::isPlaying(%p, %p)", this, fmod_channel);
|
|
if (!fmod_channel)
|
|
{
|
|
if (RBX::Network::Players::clientIsPresent(this, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return playCount > 0;
|
|
}
|
|
|
|
bool playing = false;
|
|
bool paused = false;
|
|
if (!invalidChannel)
|
|
{
|
|
FMOD_RESULT fmodResult = fmod_channel->isPlaying(&playing);
|
|
|
|
if (FMOD_OK != fmodResult)
|
|
{
|
|
invalidChannel = true;
|
|
SoundService::checkResultNoThrow(fmodResult, "isPlaying", this, fmod_channel);
|
|
playing = false;
|
|
}
|
|
|
|
fmodResult = fmod_channel->getPaused(&paused);
|
|
if (FMOD_OK != fmodResult)
|
|
{
|
|
invalidChannel = true;
|
|
SoundService::checkResultNoThrow(fmodResult, "isPlaying", this, fmod_channel);
|
|
playing = false;
|
|
}
|
|
|
|
FASTLOG2(DFLog::SoundTrace, "Returning SoundChannel::isPlaying(%p) = %d", this, playing && !paused);
|
|
}
|
|
|
|
return playing && !paused;
|
|
}
|
|
|
|
bool SoundChannel::isSoundLoaded() const
|
|
{
|
|
return sound != NULL && sound->id == getSoundId();
|
|
}
|
|
|
|
void SoundChannel::loadSound(const Instance *context, bool shouldPlayOnLoad)
|
|
{
|
|
FASTLOG3(DFLog::SoundTrace, "SoundChannel::loadSound(%p, %p, %d)", this, context, shouldPlayOnLoad);
|
|
if (!SoundService::soundDisabled && !soundDisabled && RBX::GameSettings::singleton().soundEnabled)
|
|
{
|
|
ContentProvider* contentProvider = ServiceProvider::create<ContentProvider>(context);
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::loadSound(%p), contentProvider = %p", this, contentProvider);
|
|
if (contentProvider)
|
|
{
|
|
if (!isSoundLoaded())
|
|
{
|
|
if (contentProvider->hasContent(getSoundId()))
|
|
{
|
|
FASTLOGS(FLog::Sound, "Loading cached soundId = %s", getSoundId().c_str());
|
|
onSoundLoaded(context, shouldPlayOnLoad);
|
|
}
|
|
else
|
|
{
|
|
FASTLOGS(FLog::Sound, "Fetching soundId = %s", getSoundId().c_str());
|
|
contentProvider->preloadContentWithCallback(soundId, ContentProvider::PRIORITY_SOUND,
|
|
boost::bind(&onSoundLoadedForSoundChannel, shared_from(this), soundId, context, shouldPlayOnLoad, _1, shared_ptr<const std::string>()),
|
|
AsyncHttpQueue::AsyncWrite,
|
|
FString::AssetTypeHeaderForSounds
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FASTLOGS(FLog::Sound, "Already loaded soundId = %s", getSoundId().c_str());
|
|
onSoundLoaded(context, shouldPlayOnLoad);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundChannel::setSoundId(SoundId value)
|
|
{
|
|
if (DFLog::SoundTrace)
|
|
{
|
|
std::stringstream ss;
|
|
ss << reinterpret_cast<void*>(this) << ", " << value.toString() << ")";
|
|
FASTLOGS(DFLog::SoundTrace, "SoundChannel::setSoundId(%s", ss.str().c_str());
|
|
}
|
|
|
|
if (value != soundId)
|
|
{
|
|
soundId = value;
|
|
releaseChannel();
|
|
loadSound(this, false);
|
|
raiseChanged(sound_desc_SoundId);
|
|
}
|
|
}
|
|
|
|
const SoundId &SoundChannel::getSoundId() const
|
|
{
|
|
return soundId;
|
|
}
|
|
|
|
float SoundChannel::getVolume() const
|
|
{
|
|
return volume;
|
|
}
|
|
|
|
double SoundChannel::getSoundLength() const
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::getSoundLength(%p, %p)", this, fmod_channel);
|
|
|
|
unsigned length = 0;
|
|
FMOD::Sound* fmod_sound = sound ? sound->tryLoad(this) : NULL;
|
|
if (fmod_sound)
|
|
{
|
|
FMOD_RESULT fmodResult = fmod_sound->getLength(&length, FMOD_TIMEUNIT_MS);
|
|
if (FMOD_OK != fmodResult)
|
|
{
|
|
SoundService::checkResultNoThrow(fmodResult, "getLength", this, fmod_sound);
|
|
length = 0;
|
|
}
|
|
}
|
|
return static_cast<double>(length/1000.0);
|
|
}
|
|
|
|
double SoundChannel::getSoundPosition() const
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::getSoundPosition(%p, %p)", this, fmod_channel);
|
|
double position = 0.0;
|
|
|
|
// server emulates sound position, has no actual fmod channels
|
|
if (controlledByAndIsServer())
|
|
{
|
|
position = soundPositionSeconds;
|
|
}
|
|
else
|
|
{
|
|
unsigned positionMs = 0;
|
|
FMOD_RESULT fmodResult = fmod_channel->getPosition(&positionMs, FMOD_TIMEUNIT_MS);
|
|
if (FMOD_OK != fmodResult)
|
|
{
|
|
invalidChannel = true;
|
|
SoundService::checkResultNoThrow(fmodResult, "getPosition", this, fmod_channel);
|
|
position = 0;
|
|
}
|
|
else
|
|
{
|
|
position = ((double)positionMs)/1000.0;
|
|
}
|
|
}
|
|
|
|
double soundLength = getSoundLength();
|
|
position = fmod(position, (soundLength ? soundLength : 1));
|
|
|
|
if (DFLog::SoundTiming)
|
|
{
|
|
std::stringstream ss;
|
|
ss << reinterpret_cast<const void*>(this) << ", " << getSoundId().toString() << ") = " << position;
|
|
FASTLOGS(DFLog::SoundTiming, "Returning SoundChannel::getSoundPosition(%s", ss.str().c_str());
|
|
}
|
|
|
|
return position;
|
|
}
|
|
|
|
|
|
void SoundChannel::setSoundPositionLua(double value)
|
|
{
|
|
if (isHeardGlobally() && !RBX::Network::Players::serverIsPresent(this))
|
|
{
|
|
if (RBX::Network::Players::clientIsPresent(this))
|
|
{
|
|
throw std::runtime_error("Sound.TimePosition was set from local script while either in Workspace or SoundService. Only use a server script to set TimePosition when a sound is in these locations.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
setSoundPosition(value, true);
|
|
}
|
|
|
|
// Set our sound position, but if the value passed in is the same as soundPositionMillisecs, then try to set FMOD's
|
|
// position without replicating the value back to everyone else. See SoundChannel::playSound for usage of this
|
|
// latter feature.
|
|
void SoundChannel::setSoundPosition(double value, bool setFromLua)
|
|
{
|
|
FASTLOG3(DFLog::SoundTrace, "SoundChannel::setSoundPosition(%p, %p, %d)", this, fmod_channel, (int)value);
|
|
if (fmod_channel)
|
|
{
|
|
double oldPosition = getSoundPosition();
|
|
double soundLength = getSoundLength();
|
|
double newPosition = fmod(value, (soundLength ? soundLength : 1.0));
|
|
if (oldPosition != newPosition)
|
|
{
|
|
FASTLOG1F(FLog::Sound, "Setting sound position on channel to %f", newPosition);
|
|
SoundService::checkResultNoThrow(fmod_channel->setPosition((newPosition * 1000.0), FMOD_TIMEUNIT_MS), "setPosition", this, fmod_channel);
|
|
}
|
|
}
|
|
|
|
|
|
// Only send a property update if our property really changed. This is a bit of trickery to allow
|
|
// clients to set their positions at "join time" without re-replicating the value
|
|
// back out to others.
|
|
if (value != soundPositionSeconds)
|
|
{
|
|
soundPositionSeconds = value;
|
|
raiseChanged(sound_desc_SoundPosition);
|
|
|
|
if (controlledByAndIsServer() && (getSoundLength() >= FInt::MinSecondLengthForLongSoundChannel))
|
|
{
|
|
if (setFromLua)
|
|
{
|
|
event_timePositionUpdatedFromServerScript.fireAndReplicateEvent(this, soundPositionSeconds);
|
|
}
|
|
else if ( lastTimePosReplication.delta().msec() > FInt::MinMsecBetweenTimePosEventReplication )
|
|
{
|
|
// todo: just always fire this event, and only replicated it to clients that are actually listening
|
|
lastTimePosReplication.reset();
|
|
event_timePositionUpdatedFromServer.fireAndReplicateEvent(this, soundPositionSeconds);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundChannel::setVolume(float value)
|
|
{
|
|
value = G3D::clamp(value, 0.0f, 1.0f);
|
|
if (DFLog::SoundTrace)
|
|
{
|
|
std::stringstream ss;
|
|
ss << reinterpret_cast<void*>(this) << ", " << value << ")";
|
|
FASTLOGS(DFLog::SoundTrace, "SoundChannel::setVolume(%s", ss.str().c_str());
|
|
}
|
|
|
|
if (volume != value)
|
|
{
|
|
volume = value;
|
|
|
|
if (fmod_channel)
|
|
{
|
|
SoundService::checkResultNoThrow(fmod_channel->setVolume(value), "setVolume", this, fmod_channel);
|
|
}
|
|
|
|
raiseChanged(sound_desc_Volume);
|
|
}
|
|
}
|
|
|
|
float SoundChannel::getPitch() const
|
|
{
|
|
return pitch;
|
|
}
|
|
|
|
void SoundChannel::setPitch(float value)
|
|
{
|
|
if (value < 0.0f)
|
|
{
|
|
value = 0.0f;
|
|
}
|
|
|
|
if (pitch != value)
|
|
{
|
|
pitch = value;
|
|
|
|
if (fmod_channel)
|
|
{
|
|
SoundService::checkResultNoThrow(fmod_channel->setFrequency(pitch * defaultFrequency), "setFrequency", this, fmod_channel);
|
|
}
|
|
|
|
raiseChanged(sound_desc_Pitch);
|
|
}
|
|
}
|
|
|
|
float SoundChannel::getMinDistance() const
|
|
{
|
|
if (!DFFlag::MinMaxDistanceEnabled)
|
|
{
|
|
return 0;
|
|
}
|
|
return minDistance;
|
|
}
|
|
|
|
void SoundChannel::setMinDistance(float value)
|
|
{
|
|
if (value < 0.0f)
|
|
{
|
|
value = 0.0f;
|
|
}
|
|
|
|
if (minDistance != value)
|
|
{
|
|
minDistance = value;
|
|
raiseChanged(sound_desc_MinDistance);
|
|
}
|
|
}
|
|
|
|
float SoundChannel::getMaxDistance() const
|
|
{
|
|
if (!DFFlag::MinMaxDistanceEnabled)
|
|
{
|
|
return 0;
|
|
}
|
|
return maxDistance;
|
|
}
|
|
|
|
void SoundChannel::setMaxDistance(float value)
|
|
{
|
|
if (value < 0.0f)
|
|
{
|
|
value = 0.0f;
|
|
}
|
|
|
|
if (maxDistance != value)
|
|
{
|
|
maxDistance = value;
|
|
raiseChanged(sound_desc_MaxDistance);
|
|
}
|
|
}
|
|
|
|
void SoundChannel::setRollOffMode(Soundscape::RollOffMode value)
|
|
{
|
|
if (!DFFlag::RollOffModeEnabled) {
|
|
return;
|
|
}
|
|
|
|
if (rollOff != value) {
|
|
rollOff = value;
|
|
// DFFLag::RollOffModeEnabled
|
|
// raiseChanged(sound_desc_RollOffMode);
|
|
}
|
|
}
|
|
|
|
bool SoundChannel::getLooped() const
|
|
{
|
|
return looped;
|
|
}
|
|
|
|
void SoundChannel::updateLooped()
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::updateLooped(%p, %p)", this, fmod_channel);
|
|
if (fmod_channel)
|
|
{
|
|
FMOD_MODE mode;
|
|
SoundService::checkResultNoThrow(fmod_channel->getMode(&mode), "getMode", this, fmod_channel);
|
|
mode |= (getLooped() ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF);
|
|
mode &= (getLooped() ? ~FMOD_LOOP_OFF : ~FMOD_LOOP_NORMAL);
|
|
SoundService::checkResultNoThrow(fmod_channel->setMode(mode), "setMode", this, fmod_channel);
|
|
SoundService::checkResultNoThrow(fmod_channel->setLoopCount(getLooped() ? -1 : 0), "setLoopCount", this, fmod_channel);
|
|
|
|
if (FInt::MinSecondLengthForLongSoundChannel > 0)
|
|
{
|
|
FASTLOGS(FLog::Sound, "Updating channel priority on soundId = %s", getSoundId().c_str());
|
|
|
|
if (FInt::MinSecondLengthForLongSoundChannel <= getSoundLength())
|
|
{
|
|
SoundService::checkResult(fmod_channel->setPriority(FMOD_LONG_SOUND_CHANNEL_PRIORITY), "setPriority", this, fmod_channel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundChannel::setLooped(bool value)
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::setLooped(%p, %d)", this, value);
|
|
if (looped!=value)
|
|
{
|
|
looped = value;
|
|
updateLooped();
|
|
|
|
raiseChanged(sound_desc_Looped);
|
|
}
|
|
}
|
|
|
|
void SoundChannel::setPlayCount(int value)
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::setPlayCount(%p, %d)", this, value);
|
|
// TODO: This isn't very satisfying. This won't replicate if you call "play" multiple times
|
|
switch (value)
|
|
{
|
|
case -1:
|
|
if (isHeardGlobally() && RBX::Network::Players::clientIsPresent(this))
|
|
{
|
|
soundPositionSeconds = 0;
|
|
}
|
|
stop();
|
|
break;
|
|
case 0:
|
|
if (isHeardGlobally() && RBX::Network::Players::clientIsPresent(this))
|
|
{
|
|
soundPositionSeconds = getSoundPosition();
|
|
}
|
|
pause();
|
|
break;
|
|
default:
|
|
if (value > reqPlayCount)
|
|
{
|
|
reqPlayCount = playCount = value;
|
|
this->raiseChanged(sound_desc_PlayCount);
|
|
|
|
SoundService *soundService = ServiceProvider::create<SoundService>(this);
|
|
if (!soundService)
|
|
{
|
|
--playCount; // In onServiceProvider, we will have another chance to play.
|
|
}
|
|
else
|
|
{
|
|
FASTLOG3(DFLog::SoundTrace, "SoundChannel(%p) soundService = %p, enabled = %d", this, soundService, soundService->enabled());
|
|
}
|
|
|
|
loadSound(this, true);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void SoundChannel::update3D(FMOD::Channel* channel)
|
|
{
|
|
if (is3D && part)
|
|
{
|
|
if (DFFlag::MinMaxDistanceEnabled)
|
|
{
|
|
channel->set3DMinMaxDistance(minDistance, maxDistance);
|
|
}
|
|
if (DFFlag::RollOffModeEnabled)
|
|
{
|
|
if (rollOff == Inverse)
|
|
{
|
|
channel->setMode(FMOD_3D_INVERSEROLLOFF);
|
|
}
|
|
else if (rollOff == Linear)
|
|
{
|
|
channel->setMode(FMOD_3D_LINEARROLLOFF);
|
|
}
|
|
}
|
|
FMOD_VECTOR pos;
|
|
if (SoundService::convert(part->getCoordinateFrame().translation, pos))
|
|
{
|
|
FMOD_VECTOR vel;
|
|
|
|
if (SoundService::convert(part->getLinearVelocity(), vel))
|
|
{
|
|
FMOD_RESULT fmodResult = channel->set3DAttributes(&pos, &vel);
|
|
if (FMOD_OK != fmodResult) // don't overlog during traces
|
|
{
|
|
SoundService::checkResultNoThrow(fmodResult, "set3DAttributes", this, channel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundChannel::soundEnded(weak_ptr<SoundChannel> channelWeak, std::string soundId)
|
|
{
|
|
if (shared_ptr<SoundChannel> channel = channelWeak.lock())
|
|
{
|
|
channel->soundEndedSignal(soundId);
|
|
}
|
|
}
|
|
|
|
void SoundChannel::onChannelEnd(const FMOD_CHANNEL *channel)
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::onChannelEnd(%p, %p)", this, fmod_channel);
|
|
if (fmod_channel && reinterpret_cast<const void*>(channel) == reinterpret_cast<const void*>(fmod_channel))
|
|
{
|
|
FASTLOG2(FLog::Sound, "Sound ended for channel = %p, fmod_channel = %p", this, fmod_channel);
|
|
FASTLOGS(FLog::Sound, "Sound ended for soundId = %s", getSoundId().c_str());
|
|
|
|
SoundService::checkResultNoThrow(fmod_channel->setUserData(reinterpret_cast<void*>(NULL)), "setUserData onChannelEnd", this, fmod_channel);
|
|
fmod_channel = NULL;
|
|
|
|
if (sound)
|
|
{
|
|
sound->unacquire();
|
|
if (DataModel* dm = RBX::DataModel::get(this))
|
|
{
|
|
dm->submitTask(boost::bind(&SoundChannel::soundEnded, shared_from(this), getSoundId().toString()), RBX::DataModelJob::Write);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FMOD_RESULT F_CALLBACK callbackChannelEnd(FMOD_CHANNELCONTROL *channelControl, FMOD_CHANNELCONTROL_TYPE type, FMOD_CHANNELCONTROL_CALLBACK_TYPE callbacktype, void *commanddata1, void *commanddata2)
|
|
{
|
|
FMOD_RESULT fmodResult = FMOD_OK;
|
|
|
|
if(type == FMOD_CHANNELCONTROL_CHANNEL)
|
|
{
|
|
//Channel specific functions here
|
|
FMOD_CHANNEL* channel = (FMOD_CHANNEL *)channelControl;
|
|
if (callbacktype==FMOD_CHANNELCONTROL_CALLBACK_END)
|
|
{
|
|
// TODO: For some reason not all channels call this function... I have no idea why. The result is that we have some sound "leaks" reported by getSoundStats()
|
|
RBX::Soundscape::SoundChannel* sound;
|
|
FMOD_RESULT fmodResult = FMOD_Channel_GetUserData(channel, reinterpret_cast<void**>(&sound));
|
|
SoundService::checkResultNoThrow(fmodResult, "FMOD_Channel_GetUserData", NULL, channel);
|
|
if (FMOD_OK == fmodResult && sound)
|
|
{
|
|
sound->onChannelEnd(channel);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ChannelGroup specific functions here
|
|
// FMOD::ChannelGroup *group = (FMOD::ChannelGroup *)channelControl;
|
|
// do we need to handle channel groups?
|
|
|
|
}
|
|
|
|
|
|
return fmodResult;
|
|
}
|
|
|
|
void SoundChannel::playSound(bool isResuming)
|
|
{
|
|
FASTLOG1(DFLog::SoundTrace, "SoundChannel::playSound(%p)", this);
|
|
playSound(this, isResuming);
|
|
}
|
|
|
|
bool SoundChannel::isHeardGlobally() const
|
|
{
|
|
FASTLOG1(DFLog::SoundTrace, "SoundChannel::isHeardGlobally(%p)", this);
|
|
|
|
if (Workspace* workspace = RBX::ServiceProvider::find<Workspace>(this))
|
|
{
|
|
return isDescendantOf(workspace);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SoundChannel::isHeardLocally(const Instance* context) const
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::isHeardLocally(%p, %p)", this, context);
|
|
const Network::Player* player = Network::Players::findConstLocalPlayer(context);
|
|
|
|
if (context == this)
|
|
{
|
|
context = context->getParent();
|
|
}
|
|
|
|
const Instance* parent = context;
|
|
while (parent)
|
|
{
|
|
// Local stock sounds play
|
|
if (Instance::fastDynamicCast<const SoundService>(parent))
|
|
return true;
|
|
|
|
// Sounds inside the Workspace may always play
|
|
if (Instance::fastDynamicCast<const Workspace>(parent))
|
|
return true;
|
|
|
|
// Sounds inside the CoreGui may always play
|
|
if (Instance::fastDynamicCast<const CoreGuiService>(parent))
|
|
return true;
|
|
|
|
// Sounds inside the local Player may play
|
|
if (parent == player)
|
|
return true;
|
|
|
|
parent = parent->getParent();
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
void SoundChannel::playSound(const Instance* context, bool isResuming)
|
|
{
|
|
FASTLOG4(DFLog::SoundTrace, "SoundChannel::playSound(%p, %p, %p, %d)", this, context, fmod_channel, soundDisabled);
|
|
if (!soundDisabled)
|
|
{
|
|
try
|
|
{
|
|
SoundService* soundService = ServiceProvider::create<SoundService>(context);
|
|
|
|
if (!isHeardLocally(context))
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "Context %p is not heard locally for %p.", context, this);
|
|
return;
|
|
}
|
|
|
|
if (fmod_channel && getLooped() && isPlaying())
|
|
{
|
|
update3D(fmod_channel);
|
|
|
|
FASTLOG1(FLog::Sound, "Playing a looped sound on %p.", this);
|
|
SoundService::checkResult(fmod_channel->setPaused(false), "setPaused play existing", this, fmod_channel);
|
|
}
|
|
else
|
|
{
|
|
// If the service is not reachable yet, then put this off until later
|
|
if (!soundService)
|
|
{
|
|
FASTLOG1(DFLog::SoundTrace, "SoundChannel::playSound(%p) soundService not found", this);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (!soundService->enabled())
|
|
{
|
|
FASTLOG(FLog::Sound, "Sound service is not enabled.");
|
|
soundDisabled = true;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(sound);
|
|
if (!sound) // just in case we missed some case during testing
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (fmod_channel)
|
|
{
|
|
update3D(fmod_channel);
|
|
|
|
if (isResuming)
|
|
{
|
|
if ( FMOD_OK == fmod_channel->setPaused(false) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (FMOD_OK == fmod_channel->setPosition(0, FMOD_TIMEUNIT_MS))
|
|
{
|
|
setSoundPosition(0);
|
|
if ( FMOD_OK == fmod_channel->setPaused(false) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
sound = soundService->loadSound(getSoundId(), is3D);
|
|
sound->acquire();
|
|
FMOD::Sound* fmod_sound = sound->tryLoad(context);
|
|
RBXASSERT(fmod_sound);
|
|
if (!fmod_sound)
|
|
{
|
|
FASTLOGS(FLog::Sound, "SoundId %s return no data.", getSoundId().c_str());
|
|
return;
|
|
}
|
|
|
|
if (FLog::Sound)
|
|
{
|
|
std::stringstream ss;
|
|
ss << reinterpret_cast<void*>(this) << " playing soundId " << getSoundId().toString() << ", is3D = " << is3D;
|
|
FASTLOGS(FLog::Sound, "SoundChannel %s", ss.str().c_str());
|
|
}
|
|
|
|
FMOD_RESULT fmodResult = soundService->system->playSound(fmod_sound, NULL, true, &fmod_channel);
|
|
if (FMOD_OK != fmodResult)
|
|
{
|
|
FASTLOGS(FLog::Sound, "Failed to play soundId = %s", getSoundId().c_str());
|
|
SoundService::checkResult(fmodResult, "playSound", this, soundService->system.get());
|
|
return;
|
|
}
|
|
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel %p has new fmod_channel %p", this, fmod_channel);
|
|
invalidChannel = false;
|
|
|
|
updateLooped();
|
|
|
|
SoundService::checkResult(fmod_sound->getDefaults(&defaultFrequency, NULL), "getDefaults play", this, fmod_sound);
|
|
if (pitch != 1)
|
|
{
|
|
SoundService::checkResult(fmod_channel->setFrequency(pitch * defaultFrequency), "setFrequency play", this, fmod_channel);
|
|
}
|
|
|
|
// Wire up callbacks
|
|
SoundService::checkResult(fmod_channel->setUserData(reinterpret_cast<void*>(this)), "setUserData play", this, fmod_channel);
|
|
SoundService::checkResult(fmod_channel->setCallback(&callbackChannelEnd), "setCallback play", this, fmod_channel);
|
|
|
|
// add to master group (for master volume control)
|
|
fmod_channel->setChannelGroup(soundService->getMasterChannel());
|
|
|
|
FASTLOG1F(DFLog::SoundTrace, "Volume is %f", volume);
|
|
SoundService::checkResult(fmod_channel->setVolume(volume), "setVolume play", this, fmod_channel);
|
|
|
|
// make sure our channel is in sync with our sound position data
|
|
setSoundPosition(soundPositionSeconds);
|
|
|
|
// make sure sound is in right position for volume
|
|
update3D(fmod_channel);
|
|
|
|
// Now we are ready to play!
|
|
SoundService::checkResult(fmod_channel->setPaused(false), "setPaused play", this, fmod_channel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (RBX::base_exception& e)
|
|
{
|
|
// What should we do in case of an error? Throwing this exception further might be a problem
|
|
StandardOut::singleton()->print(MESSAGE_ERROR, e);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FASTLOG(DFLog::SoundTrace, "Sound service is currently disabled.");
|
|
}
|
|
}
|
|
|
|
void SoundChannel::playLocal(const Instance *context)
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::playLocal(%p, %p)", this, context);
|
|
playSound(context);
|
|
}
|
|
|
|
void SoundChannel::playLocal()
|
|
{
|
|
playLocal(this);
|
|
}
|
|
|
|
void SoundChannel::resume()
|
|
{
|
|
if (isPaused())
|
|
{
|
|
playSound(true);
|
|
playCount = reqPlayCount = std::max(reqPlayCount + 1, 1);
|
|
if (controlledByAndIsServer())
|
|
{
|
|
event_soundResumedFromServer.fireAndReplicateEvent(this, getSoundPosition());
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundChannel::play()
|
|
{
|
|
FASTLOG1(DFLog::SoundTrace, "SoundChannel::play(%p)", this);
|
|
loadSound(this, true);
|
|
playCount = reqPlayCount = std::max(reqPlayCount + 1, 1);
|
|
this->raiseChanged(sound_desc_PlayCount);
|
|
soundPlayedSignal(getSoundId().toString());
|
|
}
|
|
|
|
void SoundChannel::pauseLocal()
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::pauseLocal(%p, %p)", this, fmod_channel);
|
|
if (fmod_channel)
|
|
{
|
|
bool currentlyPaused = false;
|
|
fmod_channel->getPaused(¤tlyPaused);
|
|
if (!currentlyPaused)
|
|
{
|
|
soundPausedSignal(getSoundId().toString());
|
|
}
|
|
|
|
SoundService::checkResultNoThrow(fmod_channel->setPaused(true), "setPaused", this, fmod_channel);
|
|
}
|
|
}
|
|
|
|
void SoundChannel::pause()
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::pause(%p, %p)", this, fmod_channel);
|
|
pauseLocal();
|
|
|
|
if (0 != reqPlayCount)
|
|
{
|
|
playCount = reqPlayCount = 0;
|
|
this->raiseChanged(sound_desc_PlayCount);
|
|
}
|
|
}
|
|
|
|
bool SoundChannel::controlledByAndIsServer() const
|
|
{
|
|
return SoundService::soundDisabled && RBX::Network::Players::serverIsPresent(this) && isHeardGlobally();
|
|
}
|
|
|
|
void SoundChannel::stop()
|
|
{
|
|
FASTLOG2(DFLog::SoundTrace, "SoundChannel::stop(%p, %p)", this, fmod_channel);
|
|
if (fmod_channel)
|
|
{
|
|
bool currentlyPaused = false;
|
|
fmod_channel->getPaused(¤tlyPaused);
|
|
if (!currentlyPaused)
|
|
{
|
|
soundStoppedSignal(getSoundId().toString());
|
|
}
|
|
|
|
SoundService::checkResultNoThrow(fmod_channel->setUserData(reinterpret_cast<void*>(NULL)), "setUserData", this, fmod_channel);
|
|
SoundService::checkResultNoThrow(fmod_channel->stop(), "stop", this, fmod_channel);
|
|
fmod_channel = NULL;
|
|
|
|
numOfTimesLooped = 0;
|
|
}
|
|
|
|
// make sure sound position gets set to the right spot
|
|
setSoundPosition(0);
|
|
|
|
if (-1 != reqPlayCount)
|
|
{
|
|
playCount = reqPlayCount = -1;
|
|
|
|
this->raiseChanged(sound_desc_PlayCount);
|
|
}
|
|
}
|
|
|
|
} // namespace Soundscape
|
|
} // namespace RBX
|
|
|
|
|
|
// Randomized Locations for hackflags
|
|
namespace RBX
|
|
{
|
|
namespace Security
|
|
{
|
|
unsigned int hackFlag2 = 0;
|
|
};
|
|
};
|