diff --git a/Engine/source/T3D/components/audio/SoundComponent.cpp b/Engine/source/T3D/components/audio/SoundComponent.cpp new file mode 100644 index 000000000..179282d2a --- /dev/null +++ b/Engine/source/T3D/components/audio/SoundComponent.cpp @@ -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()), + "@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; +} \ No newline at end of file diff --git a/Engine/source/T3D/components/audio/SoundComponent.h b/Engine/source/T3D/components/audio/SoundComponent.h new file mode 100644 index 000000000..77dfef067 --- /dev/null +++ b/Engine/source/T3D/components/audio/SoundComponent.h @@ -0,0 +1,129 @@ +//----------------------------------------------------------------------------- +// 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. +//----------------------------------------------------------------------------- + +#ifndef EXAMPLE_COMPONENT_H +#define EXAMPLE_COMPONENT_H +#pragma once + +#ifndef COMPONENT_H +#include "T3D/components/component.h" +#endif +#ifndef RENDER_COMPONENT_INTERFACE_H +#include "T3D/components/render/renderComponentInterface.h" +#endif + +class SFXSource; + +//SoundComponent +//A basic example of the various functions you can utilize to make your own component! +//This example doesn't really DO anything, persay, but you can readily copy it as a base +//and use it as a starting point for your own. +class SoundComponent : public Component, public RenderComponentInterface, public EditorInspectInterface +{ + typedef Component Parent; + +public: + enum PublicConstants + { + MaxSoundThreads = 4, ///< Should be a power of 2 + }; + + /// @name Network state masks + /// @{ + + /// + enum SoundComponentMasks + { + SoundMaskN = Parent::NextFreeMask << 6, ///< Extends + MaxSoundThreads bits + }; + + enum BaseMaskConstants + { + SoundMask = (SoundMaskN << MaxSoundThreads) - SoundMaskN, + }; + /// @name Scripted Sound + /// @{ + struct Sound { + bool play; ///< Are we playing this sound? + SimTime timeout; ///< Time until we stop playing this sound. + SFXTrack* profile; ///< Profile on server + SFXSource* sound; ///< Sound on client + Sound::Sound() + { + play = false; + timeout = 0; + profile = NULL; + sound = NULL; + } + }; + Sound mSoundThread[MaxSoundThreads]; + SFXTrack* mSoundFile[MaxSoundThreads]; + bool mPreviewSound[MaxSoundThreads]; + bool mPlay[MaxSoundThreads]; + /// @} + + SoundComponent(); + virtual ~SoundComponent(); + DECLARE_CONOBJECT(SoundComponent); + + virtual bool onAdd(); + virtual void onRemove(); + static void initPersistFields(); + static bool _previewSound(void *object, const char *index, const char *data); + static bool _autoplay(void *object, const char *index, const char *data); + + virtual U32 packUpdate(NetConnection *con, U32 mask, BitStream *stream); + virtual void unpackUpdate(NetConnection *con, BitStream *stream); + + virtual void onComponentRemove(); + virtual void onComponentAdd(); + + virtual void componentAddedToOwner(Component *comp); + virtual void componentRemovedFromOwner(Component *comp); + + virtual void onInspect(); + virtual void onEndInspect(); + + virtual void processTick(); + virtual void advanceTime(F32 dt); + virtual void interpolateTick(F32 delta); + + void prepRenderImage(SceneRenderState* state); + void _renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat); + + virtual void playAudio(U32 slotNum, SFXTrack* profile = NULL); + virtual void stopAudio(U32 slot); + virtual void updateServerAudio(); + virtual void updateAudioState(Sound& st); + virtual void updateAudioPos(); + + //why god why + virtual TSShape* getShape() { return NULL; }; + Signal< void(RenderComponentInterface*) > onShapeChanged; + virtual TSShapeInstance* getShapeInstance() { return NULL; }; + Signal< void(RenderComponentInterface*) > onShapeInstanceChanged; + virtual MatrixF getNodeTransform(S32 nodeIdx) { return MatrixF::Identity; }; + virtual Vector getNodeTransforms() { return NULL; }; + virtual void setNodeTransforms(Vector transforms) {}; +}; + +#endif \ No newline at end of file