|
@@ -0,0 +1,421 @@
|
|
|
|
+//-----------------------------------------------------------------------------
|
|
|
|
+// Copyright (c) 2012 GarageGames, LLC
|
|
|
|
+//
|
|
|
|
+// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
+// of this software and associated documentation files (the "Software"), to
|
|
|
|
+// deal in the Software without restriction, including without limitation the
|
|
|
|
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
|
|
+// sell copies of the Software, and to permit persons to whom the Software is
|
|
|
|
+// furnished to do so, subject to the following conditions:
|
|
|
|
+//
|
|
|
|
+// The above copyright notice and this permission notice shall be included in
|
|
|
|
+// all copies or substantial portions of the Software.
|
|
|
|
+//
|
|
|
|
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
|
|
+// IN THE SOFTWARE.
|
|
|
|
+//-----------------------------------------------------------------------------
|
|
|
|
+#include "T3D/components/audio/SoundComponent.h"
|
|
|
|
+#include "core/stream/bitStream.h"
|
|
|
|
+#include "sim/netConnection.h"
|
|
|
|
+
|
|
|
|
+#include "sfx/sfxSystem.h"
|
|
|
|
+#include "sfx/sfxSource.h"
|
|
|
|
+#include "sfx/sfxTrack.h"
|
|
|
|
+#include "sfx/sfxDescription.h"
|
|
|
|
+#include "T3D/sfx/sfx3DWorld.h"
|
|
|
|
+
|
|
|
|
+#include "sfx/sfxTrack.h"
|
|
|
|
+#include "sfx/sfxTypes.h"
|
|
|
|
+
|
|
|
|
+#include "renderInstance/renderPassManager.h"
|
|
|
|
+#include "gfx/gfxDrawUtil.h"
|
|
|
|
+
|
|
|
|
+// Timeout for non-looping sounds on a channel
|
|
|
|
+static SimTime sAudioTimeout = 500;
|
|
|
|
+
|
|
|
|
+extern bool gEditingMission;
|
|
|
|
+
|
|
|
|
+//////////////////////////////////////////////////////////////////////////
|
|
|
|
+// Constructor/Destructor
|
|
|
|
+//////////////////////////////////////////////////////////////////////////
|
|
|
|
+SoundComponent::SoundComponent() : Component()
|
|
|
|
+{
|
|
|
|
+ //These flags inform that, in this particular component, we network down to the client, which enables the pack/unpackData functions to operate
|
|
|
|
+ mNetworked = true;
|
|
|
|
+
|
|
|
|
+ mFriendlyName = "Sound(Component)";
|
|
|
|
+ mComponentType = "Sound";
|
|
|
|
+ mDescription = getDescriptionText("Stores up to 4 sounds for playback.");
|
|
|
|
+
|
|
|
|
+ for (U32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++) {
|
|
|
|
+ mSoundThread[slotNum].play = false;
|
|
|
|
+ mSoundThread[slotNum].profile = 0;
|
|
|
|
+ mSoundThread[slotNum].sound = 0;
|
|
|
|
+
|
|
|
|
+ mSoundFile[slotNum] = NULL;
|
|
|
|
+ mPreviewSound[slotNum] = false;
|
|
|
|
+ mPlay[slotNum] = false;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+SoundComponent::~SoundComponent()
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+IMPLEMENT_CO_NETOBJECT_V1(SoundComponent);
|
|
|
|
+
|
|
|
|
+//Standard onAdd function, for when the component is created
|
|
|
|
+bool SoundComponent::onAdd()
|
|
|
|
+{
|
|
|
|
+ if (!Parent::onAdd())
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ for (U32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
|
|
+ mPreviewSound[slotNum] = false;
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//Standard onRemove function, when the component object is deleted
|
|
|
|
+void SoundComponent::onRemove()
|
|
|
|
+{
|
|
|
|
+ Parent::onRemove();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//This is called when the component has been added to an entity
|
|
|
|
+void SoundComponent::onComponentAdd()
|
|
|
|
+{
|
|
|
|
+ Parent::onComponentAdd();
|
|
|
|
+
|
|
|
|
+ Con::printf("We were added to an entity! SoundComponent reporting in for owner entity %i", mOwner->getId());
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//This is called when the component has been removed from an entity
|
|
|
|
+void SoundComponent::onComponentRemove()
|
|
|
|
+{
|
|
|
|
+ Con::printf("We were removed from our entity! SoundComponent signing off for owner entity %i", mOwner->getId());
|
|
|
|
+ Parent::onComponentRemove();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//This is called any time a component is added to an entity. Every component currently owned by the entity is informed of the event.
|
|
|
|
+//This allows you to do dependency behavior, like collisions being aware of a mesh component, etc
|
|
|
|
+void SoundComponent::componentAddedToOwner(Component *comp)
|
|
|
|
+{
|
|
|
|
+ for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
|
|
+ {
|
|
|
|
+ if (mPlay[slotNum])
|
|
|
|
+ {
|
|
|
|
+ playAudio(slotNum, mSoundFile[slotNum]);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ Con::printf("Our owner entity has a new component being added! SoundComponent welcomes component %i of type %s", comp->getId(), comp->getClassRep()->getNameSpace());
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//This is called any time a component is removed from an entity. Every component current owned by the entity is informed of the event.
|
|
|
|
+//This allows cleanup and dependency management.
|
|
|
|
+void SoundComponent::componentRemovedFromOwner(Component *comp)
|
|
|
|
+{
|
|
|
|
+ Con::printf("Our owner entity has a removed a component! SoundComponent waves farewell to component %i of type %s", comp->getId(), comp->getClassRep()->getNameSpace());
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//Regular init persist fields function to set up static fields.
|
|
|
|
+void SoundComponent::initPersistFields()
|
|
|
|
+{
|
|
|
|
+ //addArray("Sounds", MaxSoundThreads);
|
|
|
|
+ addField("mSoundFile", TypeSFXTrackName, Offset(mSoundFile, SoundComponent), MaxSoundThreads, "If the text will not fit in the control, the deniedSound is played.");
|
|
|
|
+ addProtectedField("mPreviewSound", TypeBool, Offset(mPreviewSound, SoundComponent),
|
|
|
|
+ &_previewSound, &defaultProtectedGetFn, MaxSoundThreads, "Preview Sound", AbstractClassRep::FieldFlags::FIELD_ComponentInspectors);
|
|
|
|
+ addProtectedField("play", TypeBool, Offset(mPlay, SoundComponent),
|
|
|
|
+ &_autoplay, &defaultProtectedGetFn, MaxSoundThreads, "Whether playback of the emitter's sound should start as soon as the emitter object is added to the level.\n"
|
|
|
|
+ "If this is true, the emitter will immediately start to play when the level is loaded.");
|
|
|
|
+ //endArray("Sounds");
|
|
|
|
+ Parent::initPersistFields();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool SoundComponent::_previewSound(void *object, const char *index, const char *data)
|
|
|
|
+{
|
|
|
|
+ U32 slotNum = (index != NULL) ? dAtoui(index) : 0;
|
|
|
|
+ SoundComponent* component = reinterpret_cast< SoundComponent* >(object);
|
|
|
|
+ if (!component->mPreviewSound[slotNum])
|
|
|
|
+ component->playAudio(slotNum, component->mSoundFile[slotNum]);
|
|
|
|
+ else
|
|
|
|
+ component->stopAudio(slotNum);
|
|
|
|
+ component->mPreviewSound[slotNum] = !component->mPreviewSound[slotNum];
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+bool SoundComponent::_autoplay(void *object, const char *index, const char *data)
|
|
|
|
+{
|
|
|
|
+ U32 slotNum = (index != NULL) ? dAtoui(index) : 0;
|
|
|
|
+ SoundComponent* component = reinterpret_cast< SoundComponent* >(object);
|
|
|
|
+ component->mPlay[slotNum] = dAtoui(data);
|
|
|
|
+ if (component->mPlay[slotNum])
|
|
|
|
+ component->playAudio(slotNum, component->mSoundFile[slotNum]);
|
|
|
|
+ else
|
|
|
|
+ component->stopAudio(slotNum);
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+U32 SoundComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
|
|
|
+{
|
|
|
|
+ U32 retMask = Parent::packUpdate(con, mask, stream);
|
|
|
|
+
|
|
|
|
+ if (mask & InitialUpdateMask)
|
|
|
|
+ {
|
|
|
|
+ // mask off sounds that aren't playing
|
|
|
|
+ S32 slotNum;
|
|
|
|
+ for (slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
|
|
+ if (!mSoundThread[slotNum].play)
|
|
|
|
+ mask &= ~(SoundMaskN << slotNum);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
|
|
+ stream->writeFlag(mPreviewSound[slotNum]);
|
|
|
|
+
|
|
|
|
+ if (stream->writeFlag(mask & SoundMask))
|
|
|
|
+ {
|
|
|
|
+ for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
|
|
+ {
|
|
|
|
+ Sound& st = mSoundThread[slotNum];
|
|
|
|
+
|
|
|
|
+ if (stream->writeFlag(mask & (SoundMaskN << slotNum)))
|
|
|
|
+ {
|
|
|
|
+ if (stream->writeFlag(st.play))
|
|
|
|
+ //stream->writeRangedU32(st.profile->getId(), DataBlockObjectIdFirst,
|
|
|
|
+ // DataBlockObjectIdLast);
|
|
|
|
+ stream->writeString(st.profile->getName());
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return retMask;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void SoundComponent::unpackUpdate(NetConnection *con, BitStream *stream)
|
|
|
|
+{
|
|
|
|
+ Parent::unpackUpdate(con, stream);
|
|
|
|
+
|
|
|
|
+ for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
|
|
+ mPreviewSound[slotNum] = stream->readFlag();
|
|
|
|
+
|
|
|
|
+ if (stream->readFlag())
|
|
|
|
+ {
|
|
|
|
+ for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
|
|
+ {
|
|
|
|
+ if (stream->readFlag())
|
|
|
|
+ {
|
|
|
|
+ Sound& st = mSoundThread[slotNum];
|
|
|
|
+ st.play = stream->readFlag();
|
|
|
|
+ if (st.play)
|
|
|
|
+ {
|
|
|
|
+ //st.profile = (SFXTrack*)stream->readRangedU32(DataBlockObjectIdFirst,
|
|
|
|
+ // DataBlockObjectIdLast);
|
|
|
|
+ char profileName[255];
|
|
|
|
+ stream->readString(profileName);
|
|
|
|
+
|
|
|
|
+ if (!Sim::findObject(profileName, st.profile))
|
|
|
|
+ Con::errorf("Could not find SFXTrack");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //if (isProperlyAdded())
|
|
|
|
+ updateAudioState(st);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//This allows custom behavior in the event the owner is being edited
|
|
|
|
+void SoundComponent::onInspect()
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//This allows cleanup of the custom editor behavior if our owner stopped being edited
|
|
|
|
+void SoundComponent::onEndInspect()
|
|
|
|
+{
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//Process tick update function, natch
|
|
|
|
+void SoundComponent::processTick()
|
|
|
|
+{
|
|
|
|
+ Parent::processTick();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//Client-side advance function
|
|
|
|
+void SoundComponent::advanceTime(F32 dt)
|
|
|
|
+{
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//Client-side interpolation function
|
|
|
|
+void SoundComponent::interpolateTick(F32 delta)
|
|
|
|
+{
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void SoundComponent::prepRenderImage(SceneRenderState *state)
|
|
|
|
+{
|
|
|
|
+ if (!mEnabled || !mOwner || !gEditingMission)
|
|
|
|
+ return;
|
|
|
|
+ ObjectRenderInst* ri = state->getRenderPass()->allocInst< ObjectRenderInst >();
|
|
|
|
+
|
|
|
|
+ ri->renderDelegate.bind(this, &SoundComponent::_renderObject);
|
|
|
|
+ ri->type = RenderPassManager::RIT_Editor;
|
|
|
|
+ ri->defaultKey = 0;
|
|
|
|
+ ri->defaultKey2 = 0;
|
|
|
|
+
|
|
|
|
+ state->getRenderPass()->addInst(ri);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void SoundComponent::_renderObject(ObjectRenderInst *ri,
|
|
|
|
+ SceneRenderState *state,
|
|
|
|
+ BaseMatInstance *overrideMat)
|
|
|
|
+{
|
|
|
|
+ if (overrideMat)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ GFXStateBlockDesc desc;
|
|
|
|
+ desc.setBlend(true);
|
|
|
|
+
|
|
|
|
+ MatrixF camera = GFX->getWorldMatrix();
|
|
|
|
+ camera.inverse();
|
|
|
|
+ Point3F pos = mOwner->getPosition();
|
|
|
|
+
|
|
|
|
+ for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
|
|
+ {
|
|
|
|
+ if (mPreviewSound[slotNum])
|
|
|
|
+ {
|
|
|
|
+ Sound& st = mSoundThread[slotNum];
|
|
|
|
+ if (st.sound && st.sound->getDescription())
|
|
|
|
+ {
|
|
|
|
+ F32 minRad = st.sound->getDescription()->mMinDistance;
|
|
|
|
+ F32 falloffRad = st.sound->getDescription()->mMaxDistance;
|
|
|
|
+ SphereF sphere(pos, falloffRad);
|
|
|
|
+ if (sphere.isContained(camera.getPosition()))
|
|
|
|
+ desc.setCullMode(GFXCullNone);
|
|
|
|
+
|
|
|
|
+ GFX->getDrawUtil()->drawSphere(desc, minRad, pos, ColorI(255, 0, 255, 64));
|
|
|
|
+ GFX->getDrawUtil()->drawSphere(desc, falloffRad, pos, ColorI(128, 0, 128, 64));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void SoundComponent::playAudio(U32 slotNum, SFXTrack* _profile)
|
|
|
|
+{
|
|
|
|
+ AssertFatal(slotNum < MaxSoundThreads, "ShapeBase::playAudio() bad slot index");
|
|
|
|
+ SFXTrack* profile = (_profile != NULL) ? _profile : mSoundFile[slotNum];
|
|
|
|
+ Sound& st = mSoundThread[slotNum];
|
|
|
|
+ if (profile && (!st.play || st.profile != profile))
|
|
|
|
+ {
|
|
|
|
+ setMaskBits(SoundMaskN << slotNum);
|
|
|
|
+ st.play = true;
|
|
|
|
+ st.profile = profile;
|
|
|
|
+ updateAudioState(st);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void SoundComponent::stopAudio(U32 slotNum)
|
|
|
|
+{
|
|
|
|
+ AssertFatal(slotNum < MaxSoundThreads, "ShapeBase::stopAudio() bad slot index");
|
|
|
|
+
|
|
|
|
+ Sound& st = mSoundThread[slotNum];
|
|
|
|
+ if (st.play)
|
|
|
|
+ {
|
|
|
|
+ st.play = false;
|
|
|
|
+ setMaskBits(SoundMaskN << slotNum);
|
|
|
|
+ updateAudioState(st);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void SoundComponent::updateServerAudio()
|
|
|
|
+{
|
|
|
|
+ // Timeout non-looping sounds
|
|
|
|
+ for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
|
|
+ {
|
|
|
|
+ Sound& st = mSoundThread[slotNum];
|
|
|
|
+ if (st.play && st.timeout && st.timeout < Sim::getCurrentTime())
|
|
|
|
+ {
|
|
|
|
+ //clearMaskBits(SoundMaskN << slotNum);
|
|
|
|
+ st.play = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void SoundComponent::updateAudioState(Sound& st)
|
|
|
|
+{
|
|
|
|
+ SFX_DELETE(st.sound);
|
|
|
|
+
|
|
|
|
+ if (st.play && st.profile)
|
|
|
|
+ {
|
|
|
|
+ if (isClientObject())
|
|
|
|
+ {
|
|
|
|
+ //if (Sim::findObject(SimObjectId((uintptr_t)st.profile), st.profile))
|
|
|
|
+ // {
|
|
|
|
+ st.sound = SFX->createSource(st.profile, &mOwner->getTransform());
|
|
|
|
+ if (st.sound)
|
|
|
|
+ st.sound->play();
|
|
|
|
+ //}
|
|
|
|
+ else
|
|
|
|
+ st.play = false;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Non-looping sounds timeout on the server
|
|
|
|
+ st.timeout = 0;
|
|
|
|
+ if (!st.profile->getDescription()->mIsLooping)
|
|
|
|
+ st.timeout = Sim::getCurrentTime() + sAudioTimeout;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ st.play = false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void SoundComponent::updateAudioPos()
|
|
|
|
+{
|
|
|
|
+ for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
|
|
|
|
+ {
|
|
|
|
+ SFXSource* source = mSoundThread[slotNum].sound;
|
|
|
|
+ if (source)
|
|
|
|
+ source->setTransform(mOwner->getTransform());
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//----------------------------------------------------------------------------
|
|
|
|
+DefineEngineMethod(SoundComponent, playAudio, bool, (S32 slot, SFXTrack* track), (0, nullAsType<SFXTrack*>()),
|
|
|
|
+ "@brief Attach a sound to this shape and start playing it.\n\n"
|
|
|
|
+
|
|
|
|
+ "@param slot Audio slot index for the sound (valid range is 0 - 3)\n" // 3 = ShapeBase::MaxSoundThreads-1
|
|
|
|
+ "@param track SFXTrack to play\n"
|
|
|
|
+ "@return true if the sound was attached successfully, false if failed\n\n"
|
|
|
|
+
|
|
|
|
+ "@see stopAudio()\n")
|
|
|
|
+{
|
|
|
|
+ if (track && slot >= 0 && slot < SoundComponent::MaxSoundThreads) {
|
|
|
|
+ object->playAudio(slot, track);
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+DefineEngineMethod(SoundComponent, stopAudio, bool, (S32 slot), ,
|
|
|
|
+ "@brief Stop a sound started with playAudio.\n\n"
|
|
|
|
+
|
|
|
|
+ "@param slot audio slot index (started with playAudio)\n"
|
|
|
|
+ "@return true if the sound was stopped successfully, false if failed\n\n"
|
|
|
|
+
|
|
|
|
+ "@see playAudio()\n")
|
|
|
|
+{
|
|
|
|
+ if (slot >= 0 && slot < SoundComponent::MaxSoundThreads) {
|
|
|
|
+ object->stopAudio(slot);
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+}
|