MovieSceneAkAudioEventTemplate.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /*******************************************************************************
  2. The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
  3. Technology released in source code form as part of the game integration package.
  4. The content of this file may not be used without valid licenses to the
  5. AUDIOKINETIC Wwise Technology.
  6. Note that the use of the game engine is subject to the Unreal(R) Engine End User
  7. License Agreement at https://www.unrealengine.com/en-US/eula/unreal
  8. License Usage
  9. Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
  10. this file in accordance with the end user license agreement provided with the
  11. software or, alternatively, in accordance with the terms contained
  12. in a written agreement between you and Audiokinetic Inc.
  13. Copyright (c) 2023 Audiokinetic Inc.
  14. *******************************************************************************/
  15. #include "MovieSceneAkAudioEventTemplate.h"
  16. #include "AkAudioDevice.h"
  17. #include "MovieSceneAkAudioEventSection.h"
  18. #include "MovieSceneExecutionToken.h"
  19. #include "IMovieScenePlayer.h"
  20. #include "Engine/World.h"
  21. /** Defines the behaviour of an AKAudioEventSection within UE sequencer. */
  22. struct FMovieSceneAkAudioEventSectionData
  23. {
  24. FMovieSceneAkAudioEventSectionData(const UMovieSceneAkAudioEventSection& InSection)
  25. : EventTracker(InSection.EventTracker)
  26. {
  27. EventTracker->bStopAtSectionEnd = InSection.EventShouldStopAtSectionEnd();
  28. EventTracker->EventName = InSection.GetEventName();
  29. EventTracker->Event = InSection.GetEvent();
  30. EventTracker->ClipStartTime = InSection.GetStartTime();
  31. EventTracker->ClipEndTime = InSection.GetEndTime();
  32. EventTracker->EventDuration = InSection.GetEventDuration();
  33. EventTracker->EmptyPlayingIDs();
  34. EventTracker->EmptyScheduledStops();
  35. RetriggerEvent = InSection.ShouldRetriggerEvent();
  36. }
  37. float inline GetTimeInSeconds(const FMovieSceneContext& Context)
  38. {
  39. return (float)Context.GetFrameRate().AsSeconds(Context.GetTime());
  40. }
  41. void Update(const FMovieSceneContext& Context, const FMovieSceneEvaluationOperand& Operand, IMovieScenePlayer& Player, FAkAudioDevice* AudioDevice)
  42. {
  43. ensure(AudioDevice != nullptr);
  44. switch (Player.GetPlaybackStatus())
  45. {
  46. case EMovieScenePlayerStatus::Paused:
  47. case EMovieScenePlayerStatus::Stopped:
  48. {
  49. ResetTracker(AudioDevice);
  50. break;
  51. }
  52. case EMovieScenePlayerStatus::Playing:
  53. {
  54. /* We use a slight hack to support looping in the UE sequencer, by checking If our current time is <= the previous event start time.*/
  55. const float CurrentTime = GetTimeInSeconds(Context);
  56. bool bSequencerHasLooped = CurrentTime <= EventTracker->PreviousPlayingTime;
  57. /* If the section is played and no Wwise event has been triggered */
  58. if ((Context.GetDirection() == EPlayDirection::Forwards && !EventTracker->IsPlaying()) || bSequencerHasLooped)
  59. {
  60. /* If the sequencer has looped, we want to kill any currently playing events */
  61. if (EventTracker->IsPlaying() && bSequencerHasLooped)
  62. WwiseEventTriggering::StopAllPlayingIDs(AudioDevice, *EventTracker);
  63. /* If the section has a valid object binding */
  64. if (Operand.ObjectBindingID.IsValid())
  65. {
  66. /* If the Wwise event hasn't been previously triggered */
  67. if (EventTracker->PreviousEventStartTime == -1.0f || bSequencerHasLooped)
  68. ObjectBindingPlay(AudioDevice, Player.FindBoundObjects(Operand), Context);
  69. else if (RetriggerEvent)
  70. ObjectBindingRetrigger(AudioDevice, Player.FindBoundObjects(Operand), Context);
  71. }
  72. else /* Otherwwise play or re-trigger the Wwise event on a dummy object. */
  73. {
  74. if (EventTracker->PreviousEventStartTime == -1.0f || bSequencerHasLooped)
  75. MasterPlay(AudioDevice, Context);
  76. else if (RetriggerEvent)
  77. MasterRetrigger(AudioDevice, Context);
  78. }
  79. EventTracker->PreviousPlayingTime = CurrentTime;
  80. }
  81. break;
  82. }
  83. case EMovieScenePlayerStatus::Scrubbing:
  84. case EMovieScenePlayerStatus::Stepping:
  85. case EMovieScenePlayerStatus::Jumping:
  86. {
  87. if (Operand.ObjectBindingID.IsValid())
  88. {
  89. ObjectBindingScrub(AudioDevice, Player.FindBoundObjects(Operand), Context);
  90. }
  91. else
  92. {
  93. MasterScrub(AudioDevice, Context);
  94. }
  95. break;
  96. }
  97. }
  98. }
  99. void SectionBeingDestroyed(FAkAudioDevice* AudioDevice)
  100. {
  101. AudioDevice->CancelEventCallbackCookie(EventTracker.Get());
  102. ResetTracker(AudioDevice);
  103. }
  104. void ResetTracker(FAkAudioDevice* AudioDevice)
  105. {
  106. if (EventTracker->bStopAtSectionEnd)
  107. WwiseEventTriggering::StopAllPlayingIDs(AudioDevice, *EventTracker);
  108. EventTracker->PreviousEventStartTime = -1.0f;
  109. EventTracker->PreviousPlayingTime = -1.0f;
  110. }
  111. TSharedPtr<FWwiseEventTracker> GetEventTracker() { return EventTracker; }
  112. private:
  113. /** Empty previous retriggered events, play the Wwise event, and seek to the current time. */
  114. void ObjectBindingPlay(FAkAudioDevice* AudioDevice, TArrayView<TWeakObjectPtr<>> BoundObjects, const FMovieSceneContext& Context)
  115. {
  116. if (EventTracker.IsValid() && EventShouldPlay(Context))
  117. {
  118. const float CurrentTime = GetTimeInSeconds(Context);
  119. for (auto ObjectPtr : BoundObjects)
  120. {
  121. auto Object = ObjectPtr.Get();
  122. AkPlayingID PlayingID = WwiseEventTriggering::PostEvent(Object, AudioDevice, *EventTracker, CurrentTime);
  123. WwiseEventTriggering::SeekOnEvent(Object, AudioDevice, GetProportionalTime(Context), *EventTracker, PlayingID);
  124. EventTracker->PreviousEventStartTime = CurrentTime;
  125. }
  126. }
  127. }
  128. /** Play the Wwise event, store the event start time in the event tracker, and jump to the current time. */
  129. void ObjectBindingRetrigger(FAkAudioDevice* AudioDevice, TArrayView<TWeakObjectPtr<>> BoundObjects, const FMovieSceneContext& Context)
  130. {
  131. if (EventTracker.IsValid() && EventShouldPlay(Context))
  132. {
  133. const float CurrentTime = GetTimeInSeconds(Context);
  134. for (auto ObjectPtr : BoundObjects)
  135. {
  136. auto Object = ObjectPtr.Get();
  137. AkPlayingID PlayingID = WwiseEventTriggering::PostEvent(Object, AudioDevice, *EventTracker, CurrentTime);
  138. EventTracker->PreviousEventStartTime = CurrentTime;
  139. WwiseEventTriggering::SeekOnEvent(Object, AudioDevice, GetProportionalTime(Context), *EventTracker, PlayingID);
  140. }
  141. }
  142. }
  143. /** Empty previous retriggered events, play the Wwise event, and seek to the current time. */
  144. void MasterPlay(FAkAudioDevice* AudioDevice, const FMovieSceneContext& Context)
  145. {
  146. if (EventTracker.IsValid() && EventShouldPlay(Context))
  147. {
  148. EventTracker->PreviousEventStartTime = EventTracker->ClipStartTime;
  149. const float CurrentTime = GetTimeInSeconds(Context);
  150. AkPlayingID PlayingID = WwiseEventTriggering::PostEventOnDummyObject(AudioDevice, *EventTracker, CurrentTime);
  151. WwiseEventTriggering::SeekOnEventWithDummyObject(AudioDevice, GetProportionalTime(Context), *EventTracker, PlayingID);
  152. EventTracker->PreviousEventStartTime = CurrentTime;
  153. }
  154. }
  155. /** Play the Wwise event, store the event start time in the event tracker, and jump to the current time. */
  156. void MasterRetrigger(FAkAudioDevice* AudioDevice, const FMovieSceneContext& Context)
  157. {
  158. if (EventTracker.IsValid() && EventShouldPlay(Context))
  159. {
  160. const float CurrentTime = GetTimeInSeconds(Context);
  161. AkPlayingID PlayingID = WwiseEventTriggering::PostEventOnDummyObject(AudioDevice, *EventTracker, CurrentTime);
  162. EventTracker->PreviousEventStartTime = CurrentTime;
  163. WwiseEventTriggering::SeekOnEventWithDummyObject(AudioDevice, GetProportionalTime(Context), *EventTracker, PlayingID);
  164. }
  165. }
  166. void ObjectBindingScrub(FAkAudioDevice* AudioDevice, TArrayView<TWeakObjectPtr<>> BoundObjects, const FMovieSceneContext& Context)
  167. {
  168. if (EventTracker.IsValid() && EventShouldPlay(Context))
  169. {
  170. auto ProportionalTime = GetProportionalTime(Context);
  171. const float CurrentTime = GetTimeInSeconds(Context);
  172. for (auto ObjectPtr : BoundObjects)
  173. {
  174. auto Object = ObjectPtr.Get();
  175. if (!EventTracker->IsPlaying())
  176. {
  177. WwiseEventTriggering::TriggerScrubSnippet(Object, AudioDevice, *EventTracker);
  178. EventTracker->PreviousEventStartTime = -1.0f;
  179. }
  180. else if (!EventTracker->HasScheduledStop())
  181. {
  182. WwiseEventTriggering::ScheduleStopEventsForCurrentlyPlayingIDs(AudioDevice, *EventTracker);
  183. }
  184. WwiseEventTriggering::SeekOnEvent(Object, AudioDevice, ProportionalTime, *EventTracker);
  185. }
  186. }
  187. }
  188. void MasterScrub(FAkAudioDevice* AudioDevice, const FMovieSceneContext& Context)
  189. {
  190. if (EventTracker.IsValid() && EventShouldPlay(Context))
  191. {
  192. const float CurrentTime = GetTimeInSeconds(Context);
  193. if (!EventTracker->IsPlaying())
  194. {
  195. WwiseEventTriggering::TriggerScrubSnippetOnDummyObject(AudioDevice, *EventTracker);
  196. }
  197. else if (!EventTracker->HasScheduledStop())
  198. {
  199. WwiseEventTriggering::ScheduleStopEventsForCurrentlyPlayingIDs(AudioDevice, *EventTracker);
  200. }
  201. EventTracker->PreviousEventStartTime = -1.0f;
  202. WwiseEventTriggering::SeekOnEventWithDummyObject(AudioDevice, GetProportionalTime(Context), *EventTracker);
  203. }
  204. }
  205. auto GetMaxDuration() const
  206. {
  207. const auto& DurationRange = EventTracker->EventDuration;
  208. auto MaxDuration = DurationRange.GetUpperBoundValue();
  209. if (!DurationRange.IsDegenerate() || MaxDuration == 0.0f)
  210. MaxDuration = EventTracker->CurrentDurationEstimation == -1.0f ? EventTracker->GetClipDuration() : EventTracker->CurrentDurationEstimation;
  211. return MaxDuration;
  212. }
  213. /** Checks whether the current time is less than the maximum estimated duration OR if the event is set to retrigger. */
  214. bool EventShouldPlay(const FMovieSceneContext& Context)
  215. {
  216. const double PreviousStartTime = EventTracker->PreviousEventStartTime == -1.0f ? EventTracker->ClipStartTime
  217. : EventTracker->PreviousEventStartTime;
  218. const double CurrentTime = GetTimeInSeconds(Context) - PreviousStartTime;
  219. return CurrentTime < GetMaxDuration() || RetriggerEvent;
  220. }
  221. /** Returns the current time as a proportion of the maximum duration (0 - 1) */
  222. AkReal32 GetProportionalTime(const FMovieSceneContext& Context)
  223. {
  224. if (EventTracker.IsValid())
  225. {
  226. auto MaxDuration = GetMaxDuration();
  227. if (MaxDuration > 0.0f)
  228. {
  229. const double PreviousStartTime = EventTracker->PreviousEventStartTime == -1.0f ? EventTracker->ClipStartTime : EventTracker->PreviousEventStartTime;
  230. const double CurrentTime = GetTimeInSeconds(Context) - PreviousStartTime;
  231. return (float)fmod(CurrentTime, (double)MaxDuration) / MaxDuration;
  232. }
  233. }
  234. return 0.0f;
  235. }
  236. TSharedPtr<FWwiseEventTracker> EventTracker;
  237. bool RetriggerEvent = false;
  238. };
  239. struct FAkAudioEventEvaluationData : IPersistentEvaluationData
  240. {
  241. TSharedPtr<FMovieSceneAkAudioEventSectionData> SectionData;
  242. };
  243. struct FAkAudioEventExecutionToken : IMovieSceneExecutionToken
  244. {
  245. virtual void Execute(const FMovieSceneContext& Context, const FMovieSceneEvaluationOperand& Operand,
  246. FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) override
  247. {
  248. auto AudioDevice = FAkAudioDevice::Get();
  249. if (!AudioDevice)
  250. return;
  251. auto persistentData = PersistentData.GetSectionData<FAkAudioEventEvaluationData>();
  252. TSharedPtr<FMovieSceneAkAudioEventSectionData> SectionData = persistentData.SectionData;
  253. if (SectionData.IsValid())
  254. SectionData->Update(Context, Operand, Player, AudioDevice);
  255. }
  256. };
  257. FMovieSceneAkAudioEventTemplate::FMovieSceneAkAudioEventTemplate(const UMovieSceneAkAudioEventSection* InSection)
  258. : Section(InSection)
  259. {}
  260. void FMovieSceneAkAudioEventTemplate::Evaluate(const FMovieSceneEvaluationOperand& Operand, const FMovieSceneContext& Context,
  261. const FPersistentEvaluationData& PersistentData, FMovieSceneExecutionTokens& ExecutionTokens) const
  262. {
  263. auto AudioDevice = FAkAudioDevice::Get();
  264. if (!AudioDevice)
  265. return;
  266. ExecutionTokens.Add(FAkAudioEventExecutionToken());
  267. }
  268. void FMovieSceneAkAudioEventTemplate::Setup(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const
  269. {
  270. auto AudioDevice = FAkAudioDevice::Get();
  271. if (!AudioDevice)
  272. return;
  273. if (Section)
  274. PersistentData.AddSectionData<FAkAudioEventEvaluationData>().SectionData = MakeShareable(new FMovieSceneAkAudioEventSectionData(*Section));
  275. }
  276. void FMovieSceneAkAudioEventTemplate::TearDown(FPersistentEvaluationData& PersistentData, IMovieScenePlayer& Player) const
  277. {
  278. auto AudioDevice = FAkAudioDevice::Get();
  279. if (!AudioDevice)
  280. return;
  281. TSharedPtr<FMovieSceneAkAudioEventSectionData> SectionData = PersistentData.GetSectionData<FAkAudioEventEvaluationData>().SectionData;
  282. if (SectionData.IsValid())
  283. {
  284. SectionData->SectionBeingDestroyed(AudioDevice);
  285. }
  286. }