SoundComponent.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "T3D/components/audio/SoundComponent.h"
  23. #include "core/stream/bitStream.h"
  24. #include "sim/netConnection.h"
  25. #include "sfx/sfxSystem.h"
  26. #include "sfx/sfxSource.h"
  27. #include "sfx/sfxTrack.h"
  28. #include "sfx/sfxDescription.h"
  29. #include "T3D/sfx/sfx3DWorld.h"
  30. #include "sfx/sfxTrack.h"
  31. #include "sfx/sfxTypes.h"
  32. #include "renderInstance/renderPassManager.h"
  33. #include "gfx/gfxDrawUtil.h"
  34. // Timeout for non-looping sounds on a channel
  35. static SimTime sAudioTimeout = 500;
  36. extern bool gEditingMission;
  37. //////////////////////////////////////////////////////////////////////////
  38. // Constructor/Destructor
  39. //////////////////////////////////////////////////////////////////////////
  40. SoundComponent::SoundComponent() : Component()
  41. {
  42. //These flags inform that, in this particular component, we network down to the client, which enables the pack/unpackData functions to operate
  43. mNetworked = true;
  44. mFriendlyName = "Sound(Component)";
  45. mComponentType = "Sound";
  46. mDescription = getDescriptionText("Stores up to 4 sounds for playback.");
  47. for (U32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++) {
  48. mSoundThread[slotNum].play = false;
  49. mSoundThread[slotNum].profile = 0;
  50. mSoundThread[slotNum].sound = 0;
  51. mSoundFile[slotNum] = NULL;
  52. mPreviewSound[slotNum] = false;
  53. mPlay[slotNum] = false;
  54. }
  55. }
  56. SoundComponent::~SoundComponent()
  57. {
  58. }
  59. IMPLEMENT_CO_NETOBJECT_V1(SoundComponent);
  60. //Standard onAdd function, for when the component is created
  61. bool SoundComponent::onAdd()
  62. {
  63. if (!Parent::onAdd())
  64. return false;
  65. for (U32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
  66. mPreviewSound[slotNum] = false;
  67. return true;
  68. }
  69. //Standard onRemove function, when the component object is deleted
  70. void SoundComponent::onRemove()
  71. {
  72. Parent::onRemove();
  73. }
  74. //This is called when the component has been added to an entity
  75. void SoundComponent::onComponentAdd()
  76. {
  77. Parent::onComponentAdd();
  78. Con::printf("We were added to an entity! SoundComponent reporting in for owner entity %i", mOwner->getId());
  79. }
  80. //This is called when the component has been removed from an entity
  81. void SoundComponent::onComponentRemove()
  82. {
  83. Con::printf("We were removed from our entity! SoundComponent signing off for owner entity %i", mOwner->getId());
  84. Parent::onComponentRemove();
  85. }
  86. //This is called any time a component is added to an entity. Every component currently owned by the entity is informed of the event.
  87. //This allows you to do dependency behavior, like collisions being aware of a mesh component, etc
  88. void SoundComponent::componentAddedToOwner(Component *comp)
  89. {
  90. for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
  91. {
  92. if (mPlay[slotNum])
  93. {
  94. playAudio(slotNum, mSoundFile[slotNum]);
  95. }
  96. }
  97. Con::printf("Our owner entity has a new component being added! SoundComponent welcomes component %i of type %s", comp->getId(), comp->getClassRep()->getNameSpace());
  98. }
  99. //This is called any time a component is removed from an entity. Every component current owned by the entity is informed of the event.
  100. //This allows cleanup and dependency management.
  101. void SoundComponent::componentRemovedFromOwner(Component *comp)
  102. {
  103. Con::printf("Our owner entity has a removed a component! SoundComponent waves farewell to component %i of type %s", comp->getId(), comp->getClassRep()->getNameSpace());
  104. }
  105. //Regular init persist fields function to set up static fields.
  106. void SoundComponent::initPersistFields()
  107. {
  108. //addArray("Sounds", MaxSoundThreads);
  109. addField("mSoundFile", TypeSFXTrackName, Offset(mSoundFile, SoundComponent), MaxSoundThreads, "If the text will not fit in the control, the deniedSound is played.");
  110. addProtectedField("mPreviewSound", TypeBool, Offset(mPreviewSound, SoundComponent),
  111. &_previewSound, &defaultProtectedGetFn, MaxSoundThreads, "Preview Sound", AbstractClassRep::FieldFlags::FIELD_ComponentInspectors);
  112. addProtectedField("play", TypeBool, Offset(mPlay, SoundComponent),
  113. &_autoplay, &defaultProtectedGetFn, MaxSoundThreads, "Whether playback of the emitter's sound should start as soon as the emitter object is added to the level.\n"
  114. "If this is true, the emitter will immediately start to play when the level is loaded.");
  115. //endArray("Sounds");
  116. Parent::initPersistFields();
  117. }
  118. bool SoundComponent::_previewSound(void *object, const char *index, const char *data)
  119. {
  120. U32 slotNum = (index != NULL) ? dAtoui(index) : 0;
  121. SoundComponent* component = reinterpret_cast< SoundComponent* >(object);
  122. if (!component->mPreviewSound[slotNum])
  123. component->playAudio(slotNum, component->mSoundFile[slotNum]);
  124. else
  125. component->stopAudio(slotNum);
  126. component->mPreviewSound[slotNum] = !component->mPreviewSound[slotNum];
  127. return false;
  128. }
  129. bool SoundComponent::_autoplay(void *object, const char *index, const char *data)
  130. {
  131. U32 slotNum = (index != NULL) ? dAtoui(index) : 0;
  132. SoundComponent* component = reinterpret_cast< SoundComponent* >(object);
  133. component->mPlay[slotNum] = dAtoui(data);
  134. if (component->mPlay[slotNum])
  135. component->playAudio(slotNum, component->mSoundFile[slotNum]);
  136. else
  137. component->stopAudio(slotNum);
  138. return false;
  139. }
  140. U32 SoundComponent::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
  141. {
  142. U32 retMask = Parent::packUpdate(con, mask, stream);
  143. if (mask & InitialUpdateMask)
  144. {
  145. // mask off sounds that aren't playing
  146. S32 slotNum;
  147. for (slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
  148. if (!mSoundThread[slotNum].play)
  149. mask &= ~(SoundMaskN << slotNum);
  150. }
  151. for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
  152. stream->writeFlag(mPreviewSound[slotNum]);
  153. if (stream->writeFlag(mask & SoundMask))
  154. {
  155. for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
  156. {
  157. Sound& st = mSoundThread[slotNum];
  158. if (stream->writeFlag(mask & (SoundMaskN << slotNum)))
  159. {
  160. if (stream->writeFlag(st.play))
  161. //stream->writeRangedU32(st.profile->getId(), DataBlockObjectIdFirst,
  162. // DataBlockObjectIdLast);
  163. stream->writeString(st.profile->getName());
  164. }
  165. }
  166. }
  167. return retMask;
  168. }
  169. void SoundComponent::unpackUpdate(NetConnection *con, BitStream *stream)
  170. {
  171. Parent::unpackUpdate(con, stream);
  172. for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
  173. mPreviewSound[slotNum] = stream->readFlag();
  174. if (stream->readFlag())
  175. {
  176. for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
  177. {
  178. if (stream->readFlag())
  179. {
  180. Sound& st = mSoundThread[slotNum];
  181. st.play = stream->readFlag();
  182. if (st.play)
  183. {
  184. //st.profile = (SFXTrack*)stream->readRangedU32(DataBlockObjectIdFirst,
  185. // DataBlockObjectIdLast);
  186. char profileName[255];
  187. stream->readString(profileName);
  188. if (!Sim::findObject(profileName, st.profile))
  189. Con::errorf("Could not find SFXTrack");
  190. }
  191. //if (isProperlyAdded())
  192. updateAudioState(st);
  193. }
  194. }
  195. }
  196. }
  197. //This allows custom behavior in the event the owner is being edited
  198. void SoundComponent::onInspect()
  199. {
  200. }
  201. //This allows cleanup of the custom editor behavior if our owner stopped being edited
  202. void SoundComponent::onEndInspect()
  203. {
  204. }
  205. //Process tick update function, natch
  206. void SoundComponent::processTick()
  207. {
  208. Parent::processTick();
  209. }
  210. //Client-side advance function
  211. void SoundComponent::advanceTime(F32 dt)
  212. {
  213. }
  214. //Client-side interpolation function
  215. void SoundComponent::interpolateTick(F32 delta)
  216. {
  217. }
  218. void SoundComponent::prepRenderImage(SceneRenderState *state)
  219. {
  220. if (!mEnabled || !mOwner || !gEditingMission)
  221. return;
  222. ObjectRenderInst* ri = state->getRenderPass()->allocInst< ObjectRenderInst >();
  223. ri->renderDelegate.bind(this, &SoundComponent::_renderObject);
  224. ri->type = RenderPassManager::RIT_Editor;
  225. ri->defaultKey = 0;
  226. ri->defaultKey2 = 0;
  227. state->getRenderPass()->addInst(ri);
  228. }
  229. void SoundComponent::_renderObject(ObjectRenderInst *ri,
  230. SceneRenderState *state,
  231. BaseMatInstance *overrideMat)
  232. {
  233. if (overrideMat)
  234. return;
  235. GFXStateBlockDesc desc;
  236. desc.setBlend(true);
  237. MatrixF camera = GFX->getWorldMatrix();
  238. camera.inverse();
  239. Point3F pos = mOwner->getPosition();
  240. for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
  241. {
  242. if (mPreviewSound[slotNum])
  243. {
  244. Sound& st = mSoundThread[slotNum];
  245. if (st.sound && st.sound->getDescription())
  246. {
  247. F32 minRad = st.sound->getDescription()->mMinDistance;
  248. F32 falloffRad = st.sound->getDescription()->mMaxDistance;
  249. SphereF sphere(pos, falloffRad);
  250. if (sphere.isContained(camera.getPosition()))
  251. desc.setCullMode(GFXCullNone);
  252. GFX->getDrawUtil()->drawSphere(desc, minRad, pos, ColorI(255, 0, 255, 64));
  253. GFX->getDrawUtil()->drawSphere(desc, falloffRad, pos, ColorI(128, 0, 128, 64));
  254. }
  255. }
  256. }
  257. }
  258. void SoundComponent::playAudio(U32 slotNum, SFXTrack* _profile)
  259. {
  260. AssertFatal(slotNum < MaxSoundThreads, "ShapeBase::playAudio() bad slot index");
  261. SFXTrack* profile = (_profile != NULL) ? _profile : mSoundFile[slotNum];
  262. Sound& st = mSoundThread[slotNum];
  263. if (profile && (!st.play || st.profile != profile))
  264. {
  265. setMaskBits(SoundMaskN << slotNum);
  266. st.play = true;
  267. st.profile = profile;
  268. updateAudioState(st);
  269. }
  270. }
  271. void SoundComponent::stopAudio(U32 slotNum)
  272. {
  273. AssertFatal(slotNum < MaxSoundThreads, "ShapeBase::stopAudio() bad slot index");
  274. Sound& st = mSoundThread[slotNum];
  275. if (st.play)
  276. {
  277. st.play = false;
  278. setMaskBits(SoundMaskN << slotNum);
  279. updateAudioState(st);
  280. }
  281. }
  282. void SoundComponent::updateServerAudio()
  283. {
  284. // Timeout non-looping sounds
  285. for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
  286. {
  287. Sound& st = mSoundThread[slotNum];
  288. if (st.play && st.timeout && st.timeout < Sim::getCurrentTime())
  289. {
  290. //clearMaskBits(SoundMaskN << slotNum);
  291. st.play = false;
  292. }
  293. }
  294. }
  295. void SoundComponent::updateAudioState(Sound& st)
  296. {
  297. SFX_DELETE(st.sound);
  298. if (st.play && st.profile)
  299. {
  300. if (isClientObject())
  301. {
  302. //if (Sim::findObject(SimObjectId((uintptr_t)st.profile), st.profile))
  303. // {
  304. MatrixF transform = mOwner->getTransform();
  305. st.sound = SFX->createSource(st.profile, &transform);
  306. if (st.sound)
  307. st.sound->play();
  308. //}
  309. else
  310. st.play = false;
  311. }
  312. else
  313. {
  314. // Non-looping sounds timeout on the server
  315. st.timeout = 0;
  316. if (!st.profile->getDescription()->mIsLooping)
  317. st.timeout = Sim::getCurrentTime() + sAudioTimeout;
  318. }
  319. }
  320. else
  321. st.play = false;
  322. }
  323. void SoundComponent::updateAudioPos()
  324. {
  325. for (S32 slotNum = 0; slotNum < MaxSoundThreads; slotNum++)
  326. {
  327. SFXSource* source = mSoundThread[slotNum].sound;
  328. if (source)
  329. source->setTransform(mOwner->getTransform());
  330. }
  331. }
  332. //----------------------------------------------------------------------------
  333. DefineEngineMethod(SoundComponent, playAudio, bool, (S32 slot, SFXTrack* track), (0, nullAsType<SFXTrack*>()),
  334. "@brief Attach a sound to this shape and start playing it.\n\n"
  335. "@param slot Audio slot index for the sound (valid range is 0 - 3)\n" // 3 = ShapeBase::MaxSoundThreads-1
  336. "@param track SFXTrack to play\n"
  337. "@return true if the sound was attached successfully, false if failed\n\n"
  338. "@see stopAudio()\n")
  339. {
  340. if (track && slot >= 0 && slot < SoundComponent::MaxSoundThreads) {
  341. object->playAudio(slot, track);
  342. return true;
  343. }
  344. return false;
  345. }
  346. DefineEngineMethod(SoundComponent, stopAudio, bool, (S32 slot), ,
  347. "@brief Stop a sound started with playAudio.\n\n"
  348. "@param slot audio slot index (started with playAudio)\n"
  349. "@return true if the sound was stopped successfully, false if failed\n\n"
  350. "@see playAudio()\n")
  351. {
  352. if (slot >= 0 && slot < SoundComponent::MaxSoundThreads) {
  353. object->stopAudio(slot);
  354. return true;
  355. }
  356. return false;
  357. }