MovieSceneAkAudioEventTrackEditor.cpp 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095
  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 "MovieSceneAkAudioEventTrackEditor.h"
  16. #include "AkAudioBankGenerationHelpers.h"
  17. #include "AkAudioDevice.h"
  18. #include "AkAudioStyle.h"
  19. #include "AkWaapiClient.h"
  20. #include "AssetRegistry/AssetRegistryModule.h"
  21. #include "Async/Async.h"
  22. #include "CommonMovieSceneTools.h"
  23. #include "ContentBrowserModule.h"
  24. #include "Framework/Application/SlateApplication.h"
  25. #include "Framework/MultiBox/MultiBoxBuilder.h"
  26. #include "AssetRegistry/IAssetRegistry.h"
  27. #include "IAudiokineticTools.h"
  28. #include "IContentBrowserSingleton.h"
  29. #include "ISequencerSection.h"
  30. #include "MovieScene.h"
  31. #include "MovieSceneAkAudioEventSection.h"
  32. #include "MovieSceneAkAudioEventTrack.h"
  33. #include "MovieSceneCommonHelpers.h"
  34. #include "Rendering/DrawElements.h"
  35. #include "RenderUtils.h"
  36. #include "ScopedTransaction.h"
  37. #include "SequencerSectionPainter.h"
  38. #include "SequencerUtilities.h"
  39. #include "Slate/SlateTextures.h"
  40. #include "Textures/SlateTextureData.h"
  41. #include "Wwise/WwiseProjectDatabaseDelegates.h"
  42. #define LOCTEXT_NAMESPACE "MovieSceneAkAudioEventTrackEditor"
  43. /** A viewport that maintains a waveform texture for a Wwise event. */
  44. class AkAudioWaveformViewport
  45. : public ISlateViewport
  46. {
  47. public:
  48. /** Represents a waveform texture with a given size that covers a given time range. */
  49. struct WaveformDescriptor
  50. {
  51. WaveformDescriptor()
  52. {
  53. TimeRangeInView = TRange<float>(0.0f, 0.0f);
  54. TextureSize = FVector2D(0.0f, 0.0f);
  55. }
  56. WaveformDescriptor(TRange<float> in_TimeRangeInView, FVector2D in_TextureSize)
  57. : TimeRangeInView(in_TimeRangeInView)
  58. , TextureSize(in_TextureSize)
  59. {}
  60. WaveformDescriptor(const WaveformDescriptor& in_Other)
  61. : TimeRangeInView(in_Other.TimeRangeInView)
  62. , TextureSize(in_Other.TextureSize)
  63. {}
  64. bool Equals(const WaveformDescriptor& in_Other)
  65. {
  66. if (!FMath::IsNearlyEqual(TimeRangeInView.GetLowerBoundValue(), in_Other.TimeRangeInView.GetLowerBoundValue(), 0.01f))
  67. return false;
  68. if (!FMath::IsNearlyEqual(TimeRangeInView.GetUpperBoundValue(), in_Other.TimeRangeInView.GetUpperBoundValue(), 0.01f))
  69. return false;
  70. if (!FMath::IsNearlyEqual(TextureSize.X, in_Other.TextureSize.X, 0.01f) || !FMath::IsNearlyEqual(TextureSize.Y, in_Other.TextureSize.Y, 0.01f))
  71. return false;
  72. return true;
  73. }
  74. /** The range of time within the AK audio event section that can be seen within the view. */
  75. TRange<float> TimeRangeInView;
  76. /** The size of the waveform texture. */
  77. FVector2D TextureSize;
  78. };
  79. /** AkAudioWaveformViewport Constructor. */
  80. AkAudioWaveformViewport(UMovieSceneAkAudioEventSection& InSection, AkAudioWaveformViewport::WaveformDescriptor in_Descriptor,
  81. int in_iNumPeaksRequired, int in_iLeftOverPixels, FTimeToPixel in_TimeToPixelConverter,
  82. TArray<double> in_aPeakDataCache, float InDisplayScale = 1.0f);
  83. ~AkAudioWaveformViewport()
  84. {
  85. if (ShouldRender())
  86. {
  87. BeginReleaseResource(Texture);
  88. FlushRenderingCommands();
  89. }
  90. if (Texture)
  91. {
  92. delete Texture;
  93. }
  94. }
  95. /** The AK audio event section in the UE sequencer */
  96. UMovieSceneAkAudioEventSection& Section;
  97. /** The waveform texture is created with a cache of the latest peak data for the Wwise event. */
  98. TArray<double>PeakDataCache;
  99. /** The waveform texture */
  100. class FSlateTexture2DRHIRef* Texture;
  101. /** Represents a waveform texture with a given size that covers a given time range. */
  102. WaveformDescriptor Descriptor;
  103. /** The number of peaks required depends on the zoom level, the section length, and whether the section is clipped.
  104. * If the section is clipped, we ask for some extra peaks beyond the clipped region (see FMovieSceneAkAudioEventSection::UpdatePeaks)
  105. */
  106. int NumPeaksRequired = 0;
  107. /** Pixels left over between end of smoothed waveform and end of waveform duration. */
  108. int LeftOverPixels = 0;
  109. /** Used to convert pixels to time (seconds) */
  110. FTimeToPixel TimeToPixelConverter;
  111. bool ShouldRender() const { return Descriptor.TextureSize.X > 0; }
  112. /** NumChannels should come from WAAPI data. */
  113. int NumChannels = 1;
  114. /** Amount by which waveform peaks are smoothed (SmoothingAmount pixels per peak) */
  115. static const int SmoothingAmount = 5;
  116. float DisplayScale = 1.0f;
  117. /* ISlateViewport interface */
  118. virtual FIntPoint GetSize() const override
  119. {
  120. return FIntPoint((int)Descriptor.TextureSize.X, (int)Descriptor.TextureSize.Y);
  121. }
  122. virtual class FSlateShaderResource* GetViewportRenderTargetTexture() const override;
  123. virtual bool RequiresVsync() const override { return false; }
  124. virtual bool AllowScaling() const override { return false; }
  125. private:
  126. /** Generates the waveform texture data. The texture is generated as subsequent vertical pixel strips.
  127. * Strips are first initialized (InitializeStrip()), then waveform peak data is used to generate waveform color
  128. * (GenerateWaveformStrip()). Indicator lines are then generated which indicate whether or not the event is
  129. * retriggered after it ends (GenerateIndicatorLine()).
  130. *
  131. * @param OutData - the texture data buffer to fill.
  132. */
  133. void GenerateTextureData (TArray<uint8>& OutData);
  134. /** Gets the interpolated min & max pair values between in_PeakIndex and the next peak index, according to pixel value in_XPosition.
  135. * Num pixels per min & max pair in the audio peaks is controlled by SmoothingAmount.
  136. */
  137. FVector2D GetInterpolatedMinMaxPeaks(int in_XPosition, int in_PeakIndex);
  138. /** Initializes a vertical strip in the texture data to 0. */
  139. void InitializeStrip(int32 X, int32 ChannelIndex, TArray<uint8>& OutData);
  140. /** Generates a white vertical strip in the texture. The bottom and top of the strip are determined by in_MinPeakValue and in_MaxPeakValue. */
  141. void GeneratePeakStrip(int32 X, int32 ChannelIndex, TArray<uint8>& OutData, float in_MinPeakValue, float in_MaxPeakValue);
  142. /** Generates a white vertical strip in the texture according to the X position. Returns true if X is within the waveform, false otherwise. */
  143. bool GenerateWaveformStrip(int32 X, int32 ChannelIndex, TArray<uint8>& OutData);
  144. /** Generates indicators for silence or retriggering in the event section. For silence, a horizontal line is generated. For retriggering, diagonal lines are generated. */
  145. void GenerateIndicatorLine(int32 X, int32 ChannelIndex, TArray<uint8>& OutData);
  146. /** Fills a pixel with white (if in_bDirty is false) or red (if in_bDirty is true) color. */
  147. void FillPixel (uint8* Pixel, bool in_bDirty, uint8 A = 255);
  148. /** Sets a pixel's color and opacity to 0. */
  149. void EmptyPixel (uint8* Pixel);
  150. };
  151. class FSlateShaderResource* AkAudioWaveformViewport::GetViewportRenderTargetTexture() const { return Texture; }
  152. /** Lookup a pixel in the given data buffer based on the specified X and Y */
  153. uint8* LookupPixel(TArray<uint8>& Data, int32 X, int32 YPos, int32 Width, int32 Height, int32 Channel, int32 NumChannels)
  154. {
  155. int32 Y = Height - YPos - 1;
  156. if (NumChannels == 2)
  157. {
  158. Y = Channel == 0 ? Height / 2 - YPos : Height / 2 + YPos;
  159. }
  160. int32 Index = (Y * Width + X) * GPixelFormats[PF_B8G8R8A8].BlockBytes;
  161. if (Index + 3 < Data.Num())
  162. return &Data[Index];
  163. UE_LOG(LogAudiokineticTools, Warning, TEXT("Array overrun in MovieSceneAkAudioEventTrackEditor!"));
  164. return 0;
  165. }
  166. AkAudioWaveformViewport::AkAudioWaveformViewport(UMovieSceneAkAudioEventSection& InSection, AkAudioWaveformViewport::WaveformDescriptor in_Descriptor,
  167. int in_iNumPeaksRequired, int in_iLeftOverPixels, FTimeToPixel in_TimeToPixelConverter,
  168. TArray<double> in_aPeakDataCache, float InDisplayScale /* = 1.0f */)
  169. : Section(InSection)
  170. , PeakDataCache(in_aPeakDataCache)
  171. , Texture(NULL)
  172. , Descriptor(in_Descriptor)
  173. , NumPeaksRequired(in_iNumPeaksRequired)
  174. , LeftOverPixels(in_iLeftOverPixels)
  175. , TimeToPixelConverter(in_TimeToPixelConverter)
  176. , DisplayScale(InDisplayScale)
  177. {
  178. if (ShouldRender())
  179. {
  180. uint32 Size = GetSize().X * GetSize().Y * GPixelFormats[PF_B8G8R8A8].BlockBytes;
  181. TArray<uint8> RawData;
  182. RawData.AddZeroed(Size);
  183. GenerateTextureData(RawData);
  184. FSlateTextureDataPtr BulkData(new FSlateTextureData(GetSize().X, GetSize().Y, GPixelFormats[PF_B8G8R8A8].BlockBytes, RawData));
  185. Texture = new FSlateTexture2DRHIRef(GetSize().X, GetSize().Y, PF_B8G8R8A8, BulkData, TexCreate_Dynamic);
  186. BeginInitResource(Texture);
  187. }
  188. }
  189. void AkAudioWaveformViewport::EmptyPixel(uint8* Pixel)
  190. {
  191. *Pixel = (uint8)0;
  192. *(Pixel + 1) = (uint8)0;
  193. *(Pixel + 2) = (uint8)0;
  194. *(Pixel + 3) = (uint8)0;
  195. }
  196. void AkAudioWaveformViewport::FillPixel(uint8* Pixel, bool in_bDirty, uint8 A /*= 255*/)
  197. {
  198. float AlphaPreMultiply = (float)A / 255.0f;
  199. *Pixel = (uint8)((in_bDirty ? 50 : 130) * AlphaPreMultiply); //B
  200. *(Pixel + 1) = (uint8)((in_bDirty ? 50 : 130) * AlphaPreMultiply); //G
  201. *(Pixel + 2) = (uint8)((in_bDirty ? 130 : 130) * AlphaPreMultiply); //R
  202. *(Pixel + 3) = A;
  203. }
  204. /** Initializes a vertical strip in the texture data to 0. */
  205. void AkAudioWaveformViewport::InitializeStrip(int32 X, int32 ChannelIndex, TArray<uint8>& OutData)
  206. {
  207. int32 Width = (int32)GetSize().X;
  208. int32 Height = (int32)GetSize().Y;
  209. int32 MaxAmplitude = (int32)(Height * DisplayScale);
  210. for (int32 PixelIndex = 0; PixelIndex < MaxAmplitude; ++PixelIndex)
  211. {
  212. uint8* Pixel = LookupPixel(OutData, X, PixelIndex, Width, Height, ChannelIndex, NumChannels);
  213. EmptyPixel(Pixel);
  214. }
  215. }
  216. /** Generates a white vertical strip in the texture. The bottom and top of the strip are determined by in_MinPeakValue and in_MaxPeakValue. */
  217. void AkAudioWaveformViewport::GeneratePeakStrip(int32 X, int32 ChannelIndex, TArray<uint8>& OutData, float in_MinPeakValue, float in_MaxPeakValue)
  218. {
  219. const int Height = GetSize().Y;
  220. const int ZeroLine = Height / 2;
  221. const float AbsMaxAmp = Height * DisplayScale * 0.5f;
  222. const int32 MinPixelIndex = ZeroLine + (int32)(in_MinPeakValue * AbsMaxAmp);
  223. const int32 MaxPixelIndex = ZeroLine + (int32)(in_MaxPeakValue * AbsMaxAmp);
  224. for (int32 PixelIndex = 0; PixelIndex < Height; ++PixelIndex)
  225. {
  226. uint8* Pixel = LookupPixel(OutData, X, PixelIndex, GetSize().X, Height, ChannelIndex, NumChannels);
  227. if (PixelIndex >= MinPixelIndex && PixelIndex <= MaxPixelIndex)
  228. {
  229. FillPixel(Pixel, Section.EventTracker->IsDirty);
  230. }
  231. }
  232. }
  233. /** Gets the interpolated min & max pair values between in_PeakIndex and the next peak index, according to pixel value in_XPosition.
  234. * Num pixels per min & max pair in the audio peaks is controlled by SmoothingAmount.
  235. */
  236. FVector2D AkAudioWaveformViewport::GetInterpolatedMinMaxPeaks(int in_XPosition, int in_PeakIndex)
  237. {
  238. const int NumPeaks = PeakDataCache.Num() / 2;
  239. const float CurrentMinPeak = (float)PeakDataCache[in_PeakIndex * 2];
  240. const float CurrentMaxPeak = (float)PeakDataCache[in_PeakIndex * 2 + 1];
  241. float MinPeakValue = CurrentMinPeak;
  242. float MaxPeakValue = CurrentMaxPeak;
  243. if (in_PeakIndex < NumPeaks - 1)
  244. {
  245. const float NextMinPeak = (float)PeakDataCache[(in_PeakIndex + 1) * 2];
  246. const float NextMaxPeak = (float)PeakDataCache[(in_PeakIndex + 1) * 2 + 1];
  247. const float ModX = FMath::Fmod((float)in_XPosition, SmoothingAmount);
  248. const float SmoothedSlope = FMath::SmoothStep(0.0f, 1.0f, ModX / (float)SmoothingAmount);
  249. MinPeakValue = CurrentMinPeak + (NextMinPeak - CurrentMinPeak) * SmoothedSlope;
  250. MaxPeakValue = CurrentMaxPeak + (NextMaxPeak - CurrentMaxPeak) * SmoothedSlope;
  251. }
  252. return FVector2D(MinPeakValue, MaxPeakValue);
  253. }
  254. /** Generates indicators for silence or retriggering in the event section. For silence, a horizontal line is generated. For retriggering, diagonal lines are generated. */
  255. void AkAudioWaveformViewport::GenerateIndicatorLine(int32 X, int32 ChannelIndex, TArray<uint8>& OutData)
  256. {
  257. auto iSizeY = GetSize().Y;
  258. int32 iLineY = Section.DoesEventRetrigger() ? FMath::Fmod((float)X, (float)iSizeY) : iSizeY / 2;
  259. int32 iLineHeight = Section.DoesEventRetrigger() ? 2 : 1;
  260. for (int32 i = 0; i < iLineHeight; ++i)
  261. {
  262. int32 Y = iLineY - i;
  263. if (Y >= 0 && Y < GetSize().Y && X > 0 && X < GetSize().X)
  264. {
  265. uint8* Pixel = LookupPixel(OutData, X, Y, GetSize().X, iSizeY, ChannelIndex, NumChannels);
  266. FillPixel(Pixel, false);
  267. }
  268. }
  269. }
  270. /** Generates a white vertical strip in the texture according to the X position. */
  271. bool AkAudioWaveformViewport::GenerateWaveformStrip(int32 X, int32 ChannelIndex, TArray<uint8>& OutData)
  272. {
  273. const int NumPeaks = PeakDataCache.Num() / 2;
  274. if (NumPeaks > 0)
  275. {
  276. const int NumPixelsPerPeak = ((NumPeaksRequired - NumPeaks) / NumPeaks) + 1;
  277. const int PeakIndex = (X / NumPixelsPerPeak) / SmoothingAmount;
  278. if (PeakIndex < NumPeaks)
  279. {
  280. FVector2D MinMaxPeaks = GetInterpolatedMinMaxPeaks(X, PeakIndex);
  281. GeneratePeakStrip(X, ChannelIndex, OutData, MinMaxPeaks.X, MinMaxPeaks.Y);
  282. return true;
  283. }
  284. }
  285. return false;
  286. }
  287. /** Generates the waveform texture data. The texture is generated as subsequent vertical pixel strips.
  288. * Strips are first initialized (InitializeStrip()), then waveform peak data is used to generate waveform color
  289. * (GenerateWaveformStrip()), then vertical delimiters are drawn at the beginnings and ends of event retriggers
  290. * (CheckEventEndStart()).
  291. *
  292. * @param OutData - the texture data buffer to fill.
  293. */
  294. void AkAudioWaveformViewport::GenerateTextureData(TArray<uint8>& OutData)
  295. {
  296. //Draw the longest waveform in the event, then leave a space for the event duration, then indicate retrigger or silence.
  297. int32 iXPosition = 0;
  298. if (NumPeaksRequired > 0)
  299. {
  300. bool bDrawingWaveform = true;
  301. while (iXPosition < GetSize().X && bDrawingWaveform)
  302. {
  303. for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ++ChannelIndex)
  304. {
  305. InitializeStrip(iXPosition, ChannelIndex, OutData);
  306. bDrawingWaveform = GenerateWaveformStrip(iXPosition, ChannelIndex, OutData);
  307. if (!bDrawingWaveform)
  308. break;
  309. }
  310. if (bDrawingWaveform)
  311. ++iXPosition;
  312. }
  313. /* In the case where we have some pixels left over at the end of the smoothed waveform, we fill the zero line. */
  314. const int32 SourceEnd = iXPosition + LeftOverPixels;
  315. for (int32 iLeftOver = iXPosition; iLeftOver < SourceEnd; ++iLeftOver)
  316. {
  317. if (iLeftOver >= GetSize().X)
  318. break;
  319. for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ++ChannelIndex)
  320. {
  321. uint8* Pixel = LookupPixel(OutData, iLeftOver, GetSize().Y / 2, GetSize().X, GetSize().Y, ChannelIndex, NumChannels);
  322. FillPixel(Pixel, Section.EventTracker->IsDirty);
  323. }
  324. }
  325. }
  326. /* Generate indicator lines (flat horizontal line for silence, repeating diagonal lines for retrigger). This starts after the duration of the Wwise event. */
  327. const int EventDurationEndPixel = AkMax(0, TimeToPixelConverter.SecondsToPixel(Section.GetStartTime() + Section.GetEventDuration().GetUpperBoundValue()));
  328. const int ClippedEventDurationEndPixel = AkMax(0, EventDurationEndPixel - TimeToPixelConverter.SecondsToPixel(Descriptor.TimeRangeInView.GetLowerBoundValue()));
  329. if (ClippedEventDurationEndPixel >= 0 && ClippedEventDurationEndPixel < GetSize().X && iXPosition < GetSize().X)
  330. {
  331. for (int32 X = ClippedEventDurationEndPixel; X < GetSize().X; ++X)
  332. {
  333. for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ++ChannelIndex)
  334. {
  335. GenerateIndicatorLine(X, ChannelIndex, OutData);
  336. }
  337. }
  338. }
  339. }
  340. /**
  341. * Class that draws a transform section in the sequencer.
  342. * This class maintains an AkAudioWaveformViewport and various properties about the AK audio event section within the UE sequencer.
  343. * When any of these properties change, the AkAudioWaveformViewport is regenerated.
  344. */
  345. class FMovieSceneAkAudioEventSection
  346. : public ISequencerSection
  347. {
  348. public:
  349. FMovieSceneAkAudioEventSection(UMovieSceneSection& InSection, TWeakPtr<ISequencer> InSequencer)
  350. : Section(Cast<UMovieSceneAkAudioEventSection>(&InSection))
  351. , Sequencer(InSequencer)
  352. {
  353. PeakDataCache.Empty();
  354. if (FAkWaapiClient::Get())
  355. {
  356. /* Waveform upates depend on WAAPI connection and which project is loaded. */
  357. ShouldUpdateWaveform = WaapiReconnected = FAkWaapiClient::IsProjectLoaded();
  358. /* When the correct project is loaded (and we get the project loaded callback) we resume waveform updates. */
  359. ProjectLoadedHandle = FAkWaapiClient::Get()->OnProjectLoaded.AddLambda([this]
  360. {
  361. ResumeWaveformUpdates();
  362. });
  363. /* Stop waveform updates when connection is lost (this is also called when a different project is loaded). */
  364. ConnectionLostHandle = FAkWaapiClient::Get()->OnConnectionLost.AddLambda([this]
  365. {
  366. StopWaveformUpdates();
  367. });
  368. }
  369. /* When SoundBanks are loaded, we need to update the section's source info, in case Event assets or metdata have changed */
  370. if (FAkAudioDevice::Get())
  371. {
  372. SoundbanksLoadedHandle = FWwiseProjectDatabaseDelegates::Get()->GetOnDatabaseUpdateCompletedDelegate().AddLambda([this]()
  373. {
  374. if (Section != nullptr && Section->EventTracker.IsValid())
  375. {
  376. Section->UpdateAudioSourceInfo();
  377. StoredEventName = Section->GetEventName();
  378. }
  379. });
  380. }
  381. }
  382. ~FMovieSceneAkAudioEventSection()
  383. {
  384. auto pAudioDevice = FAkAudioDevice::Get();
  385. if (pAudioDevice != nullptr)
  386. WwiseEventTriggering::StopAllPlayingIDs(pAudioDevice, *Section->EventTracker);
  387. if (ProjectLoadedHandle.IsValid())
  388. {
  389. FAkWaapiClient::Get()->OnProjectLoaded.Remove(ProjectLoadedHandle);
  390. ProjectLoadedHandle.Reset();
  391. }
  392. if (ConnectionLostHandle.IsValid())
  393. {
  394. FAkWaapiClient::Get()->OnConnectionLost.Remove(ConnectionLostHandle);
  395. ConnectionLostHandle.Reset();
  396. }
  397. if (SoundbanksLoadedHandle.IsValid())
  398. {
  399. FWwiseProjectDatabaseDelegates::Get()->GetOnDatabaseUpdateCompletedDelegate().Remove(SoundbanksLoadedHandle);
  400. }
  401. // Wait for any get peak tasks to finish.
  402. FScopeLock Lock(&FetchPeaksSection);
  403. while (PeakDataUpdating) {}
  404. }
  405. public:
  406. // ISequencerSection interface
  407. virtual UMovieSceneSection* GetSectionObject() override { return Section; }
  408. virtual FText GetSectionTitle() const override
  409. {
  410. if (Section != nullptr)
  411. {
  412. FString name = Section->GetEventName();
  413. if (name.IsEmpty())
  414. {
  415. name = TEXT("(None)");
  416. }
  417. if (Section->EventTracker->IsDirty)
  418. {
  419. name += "*";
  420. }
  421. return FText::FromString(name);
  422. }
  423. return FText::GetEmpty();
  424. }
  425. virtual int32 OnPaintSection(FSequencerSectionPainter& InPainter) const override
  426. {
  427. int32 LayerId = InPainter.PaintSectionBackground();
  428. if (WaveformViewport.IsValid() && WaveformViewport->ShouldRender())
  429. {
  430. FSlateLayoutTransform t = FSlateLayoutTransform(1.0f, FVector2D(PixelOffsetLeft, 0.0f));
  431. FSlateDrawElement::MakeViewport(
  432. InPainter.DrawElements,
  433. LayerId++,
  434. InPainter.SectionGeometry.ToPaintGeometry(t),
  435. WaveformViewport,
  436. (InPainter.bParentEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect) | ESlateDrawEffect::NoGamma,
  437. FLinearColor::White
  438. );
  439. }
  440. return LayerId;
  441. }
  442. /**
  443. * Builds up the section context menu for the outliner
  444. *
  445. * @param MenuBuilder The menu builder to change
  446. * @param ObjectBinding The object guid bound to this section
  447. */
  448. virtual void BuildSectionContextMenu(FMenuBuilder& MenuBuilder, const FGuid& ObjectBinding) override
  449. {
  450. MenuBuilder.BeginSection("Audiokinetic", LOCTEXT("AKMenu", "Audiokinetic"));
  451. {
  452. MenuBuilder.AddMenuEntry(
  453. LOCTEXT("SnapLength", "Match section length to Wwise event length"),
  454. LOCTEXT("SnapLengthTooltip", "Set the section length to equal the maximum possible duration of the Wwise event."),
  455. FSlateIcon(),
  456. FUIAction(
  457. FExecuteAction::CreateLambda([this] { return SnapSectionLengthToEventLength(); }),
  458. FCanExecuteAction::CreateLambda([this] { return Section != nullptr; })
  459. ),
  460. NAME_None,
  461. EUserInterfaceActionType::Button
  462. );
  463. }
  464. MenuBuilder.EndSection();//Audiokinetic
  465. }
  466. private:
  467. /* Callback handles. */
  468. FDelegateHandle ProjectLoadedHandle;
  469. FDelegateHandle ConnectionLostHandle;
  470. FDelegateHandle SoundbanksLoadedHandle;
  471. /** The section we are visualizing */
  472. UMovieSceneAkAudioEventSection* Section;
  473. /** The peak data that we use to construct the waveform texture.
  474. * This comes from the Section's peak data, and is copied during the UpdatePeaks function.
  475. */
  476. TArray<double> PeakDataCache;
  477. /** The waveform viewport render object. */
  478. TSharedPtr<class AkAudioWaveformViewport> WaveformViewport;
  479. /* Stored data to determine when the waveform is invalidated. */
  480. AkAudioWaveformViewport::WaveformDescriptor StoredWaveformDescriptor;
  481. float StoredPixelsPerSecond = 0.0f;
  482. bool StoredEventRetriggers = false;
  483. float StoredPixelWidth = 0.0f;
  484. float StoredStartTime = 0.0f;
  485. bool StoredSectionIsDirty = false;
  486. FString StoredEventName = "";
  487. /** Indicates where the audio data should be requested and the waveform re-created on tick when new data is available.
  488. * Should be false when there is no connection to WAAPI.
  489. */
  490. bool ShouldUpdateWaveform = false;
  491. /** This flag is used to update the peaks data and the texture when the WAAPI connection is re-established (or started).
  492. */
  493. bool WaapiReconnected = false;
  494. FCriticalSection FetchPeaksSection;
  495. FThreadSafeBool PeakDataUpdating = false;
  496. FThreadSafeBool WaveformNeedsUpdate = false;
  497. /** The amount of pixels to offset the waveform texture by (if any part of it is clipped beyond the left of the editor view). */
  498. float PixelOffsetLeft = 0.0f;
  499. /** The number of pixels "left over" at the end of the smoothed waveform. This will be between 0 and AkAudioWaveformViewport::SmoothingAmount. */
  500. int LeftOverPixels = 0;
  501. /** This depends on the zoom level, as well as the position and length of the section*/
  502. int NumPeaksRequired = 0;
  503. TWeakPtr<ISequencer> Sequencer;
  504. /** Sets the length of the section to equal the duration of the Wwise event. */
  505. void SnapSectionLengthToEventLength()
  506. {
  507. if (Section != nullptr)
  508. {
  509. Section->MatchSectionLengthToEventLength();
  510. }
  511. }
  512. /** Creates the waveform viewport according to the waveform descriptor and the TimeToPixelConverter. */
  513. void CreateWaveformViewport(FTimeToPixel in_TimeToPixelConverter, AkAudioWaveformViewport::WaveformDescriptor in_Descriptor)
  514. {
  515. if (in_Descriptor.TimeRangeInView.IsDegenerate() || in_Descriptor.TimeRangeInView.IsEmpty())
  516. WaveformViewport.Reset();
  517. else
  518. {
  519. WaveformViewport = MakeShareable(new AkAudioWaveformViewport(*Section, in_Descriptor, NumPeaksRequired, LeftOverPixels, in_TimeToPixelConverter, PeakDataCache));
  520. WaveformNeedsUpdate = false;
  521. }
  522. }
  523. /** Regenerates the waveform viewport and updates stored data about the waveform, section and event. */
  524. void RegenerateWaveform(FTimeToPixel in_TimeToPixelConverter, AkAudioWaveformViewport::WaveformDescriptor in_Descriptor)
  525. {
  526. CreateWaveformViewport(in_TimeToPixelConverter, in_Descriptor);
  527. StoredWaveformDescriptor = AkAudioWaveformViewport::WaveformDescriptor(in_Descriptor);
  528. StoredEventRetriggers = Section->DoesEventRetrigger();
  529. if (Section->EventTracker.IsValid())
  530. {
  531. StoredSectionIsDirty = Section->EventTracker->IsDirty;
  532. }
  533. }
  534. /** Calculates the required number of peaks according to the section's position and length, and the zoom level (captured in in_TimeToPixelConverter).
  535. * In a background thread, the section updates its peak data and it is then copied to the peak data cache that is used to generate the waveform viewport.
  536. */
  537. void UpdatePeaks(AkAudioWaveformViewport::WaveformDescriptor in_Descriptor, FTimeToPixel in_TimeToPixelConverter)
  538. {
  539. const float dTrimmedSourceDuration = Section->GetMaxSourceDuration();
  540. const double dSectionStart = (double)Section->GetStartTime();
  541. /* Get the timeFrom and timeTo of the portion of the source that is in view, relative to the sequencer timeline. */
  542. double dSequencerTimeFrom = AkMin((double)in_Descriptor.TimeRangeInView.GetLowerBoundValue(), dSectionStart + (double)dTrimmedSourceDuration);
  543. double dSequencerTimeTo = AkMin(dSectionStart + (double)dTrimmedSourceDuration, (double)in_Descriptor.TimeRangeInView.GetUpperBoundValue());
  544. /* Convert to Pixels. */
  545. const int NumPixelsInWaveform = in_TimeToPixelConverter.SecondsToPixel((float)dSequencerTimeTo) - in_TimeToPixelConverter.SecondsToPixel((float)dSequencerTimeFrom);
  546. /* Get the timeFrom and timeTo values relative to the source (subtract section start time and add trim begin). */
  547. const double dTrimmedSourceTimeFrom = dSequencerTimeFrom - dSectionStart + Section->GetTrimBegin();
  548. double dTrimmedSourceTimeTo = dSequencerTimeTo - dSectionStart + Section->GetTrimBegin();
  549. /* The extra peak data is for the final pixels, to ensure there is not an empty space of
  550. * (PixelsInWaveform - (NumPeaks * (PixelsInWaveform / SmoothingAmount)))
  551. * pixels at the end of the section.
  552. */
  553. const int numExtraPeaks = 2;
  554. NumPeaksRequired = (NumPixelsInWaveform / AkAudioWaveformViewport::SmoothingAmount) + numExtraPeaks;
  555. LeftOverPixels = AkMax(NumPixelsInWaveform - NumPeaksRequired * AkAudioWaveformViewport::SmoothingAmount, 0);
  556. if (NumPeaksRequired > 0 && (float)dTrimmedSourceTimeFrom < (float)dTrimmedSourceTimeTo)
  557. {
  558. PeakDataUpdating = true;
  559. AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, dTrimmedSourceTimeFrom, dTrimmedSourceTimeTo, in_Descriptor]()
  560. {
  561. FScopeLock Lock(&FetchPeaksSection);
  562. Section->UpdateAudioSourcePeaks(NumPeaksRequired, dTrimmedSourceTimeFrom, dTrimmedSourceTimeTo);
  563. Section->RequiresUpdate = false;
  564. auto newPeakData = Section->GetAudioSourcePeaks();
  565. PeakDataCache.Empty();
  566. PeakDataCache.SetNum(newPeakData.Num());
  567. memcpy(PeakDataCache.GetData(), newPeakData.GetData(), sizeof(double) * newPeakData.Num());
  568. WaveformNeedsUpdate = true;
  569. PeakDataUpdating = false;
  570. });
  571. }
  572. }
  573. FTimeToPixel CreateTimeToPixelConverter(const FGeometry& AllottedGeometry, float& out_PixelsPerSecond)
  574. {
  575. TRange<double> ViewRange(Section->GetStartTime(), Section->GetEndTime());
  576. double VisibleWidth = FMath::Min(1.0, ViewRange.Size<double>());
  577. out_PixelsPerSecond = AllottedGeometry.GetLocalSize().X / VisibleWidth;
  578. FFrameRate TickResolution = Section->GetTypedOuter<UMovieScene>()->GetTickResolution();
  579. FTimeToPixel TimeToPixelConverter(AllottedGeometry, ViewRange, TickResolution);
  580. return TimeToPixelConverter;
  581. }
  582. /** Each tick we check whether any of the stored data about the section, event, or waveform has changed such that we need to update */
  583. void Tick(const FGeometry& AllottedGeometry, const FGeometry& ParentGeometry, const double InCurrentTime, const float InDeltaTime) override
  584. {
  585. UMovieSceneTrack* Track = Section->GetTypedOuter<UMovieSceneAkAudioEventTrack>();
  586. if (Track && ShouldUpdateWaveform)
  587. {
  588. if (Section->InitState == AkEventSectionState::EInitialized)
  589. {
  590. if (StoredEventName == "")
  591. {
  592. StoredEventName = Section->GetEventName();
  593. }
  594. else if (StoredEventName != Section->GetEventName())
  595. {
  596. /** In the case where the event target for a section is changed, we need to ensure UpdateAudioSourceInfo is called before UpdateAudioSourcePeaks,
  597. * otherwise we'll try to call update peaks with invalid audio source info.
  598. */
  599. Section->UpdateAudioSourceInfo();
  600. StoredEventName = Section->GetEventName();
  601. }
  602. if (Section->AudioSourceInfoIsValid())
  603. {
  604. /** Pass on the new ScrubTailLength to the underlying section, if it has been changed via the Editor UI. */
  605. if (Section->EventTracker->ScrubTailLengthMs != Section->GetScrubTailLength())
  606. Section->EventTracker->ScrubTailLengthMs = Section->GetScrubTailLength();
  607. auto Concatenated = Concatenate(ParentGeometry.GetAccumulatedLayoutTransform(), AllottedGeometry.GetAccumulatedLayoutTransform().Inverse());
  608. const FSlateRect ParentRect = TransformRect(Concatenated, FSlateRect(FVector2D(0, 0), ParentGeometry.GetLocalSize()));
  609. const float LeftMostVisiblePixel = FMath::Max(ParentRect.Left, 0.f);
  610. const float RightMostVisiblePixel = FMath::Min(ParentRect.Right, AllottedGeometry.GetLocalSize().X);
  611. /** If the section is in view ... */
  612. if (RightMostVisiblePixel > LeftMostVisiblePixel)
  613. {
  614. float PixelsPerSecond;
  615. FTimeToPixel TimeToPixelConverter = CreateTimeToPixelConverter(AllottedGeometry, PixelsPerSecond);
  616. TRange<float> TimeRangeInView = TRange<float>(TimeToPixelConverter.PixelToSeconds(LeftMostVisiblePixel),
  617. TimeToPixelConverter.PixelToSeconds(RightMostVisiblePixel));
  618. PixelOffsetLeft = LeftMostVisiblePixel;
  619. FVector2D TextureSize((float)(RightMostVisiblePixel - LeftMostVisiblePixel), (float)AllottedGeometry.GetLocalSize().Y);
  620. AkAudioWaveformViewport::WaveformDescriptor Descriptor(TimeRangeInView, TextureSize);
  621. /** Check if the peak range has changed.
  622. * This occurs when the texture size changes, or when the section is
  623. * clipped beyond the left of the sequencer view (i.e. it's start time is out of view)
  624. * and it has been dragged.
  625. */
  626. const bool peakRangeChanged = (StoredPixelWidth != TextureSize.X) || (ParentRect.Left > 0.0f && StoredStartTime != Section->GetStartTime());
  627. if (StoredPixelsPerSecond != PixelsPerSecond || Section->RequiresUpdate || WaapiReconnected || peakRangeChanged)
  628. {
  629. StoredPixelWidth = TextureSize.X;
  630. StoredStartTime = Section->GetStartTime();
  631. /** Update the peak data if it is not currently being updated, and if the duration in view is sufficiently large. */
  632. const float minimumDuration = 0.01f;
  633. const float durationInView = TimeRangeInView.GetUpperBoundValue() - TimeRangeInView.GetLowerBoundValue();
  634. if (!PeakDataUpdating && durationInView > minimumDuration && Descriptor.TextureSize.Y > 0.0f)
  635. {
  636. UpdatePeaks(Descriptor, TimeToPixelConverter);
  637. StoredPixelsPerSecond = PixelsPerSecond;
  638. }
  639. if (WaapiReconnected)
  640. WaapiReconnected = false;
  641. }
  642. bool WaveformUpdateRequired = WaveformNeedsUpdate ||
  643. ((!StoredWaveformDescriptor.Equals(Descriptor)) && Descriptor.TextureSize.Y > 0.0f) ||
  644. StoredEventRetriggers != Section->DoesEventRetrigger();
  645. if (Section->EventTracker.IsValid())
  646. {
  647. WaveformUpdateRequired |= StoredSectionIsDirty != Section->EventTracker->IsDirty;
  648. }
  649. if (WaveformUpdateRequired)
  650. {
  651. if (!PeakDataUpdating)
  652. RegenerateWaveform(TimeToPixelConverter, Descriptor);
  653. }
  654. }
  655. }
  656. else
  657. {
  658. if (PeakDataCache.Num() > 0)
  659. {
  660. FScopeLock Lock(&FetchPeaksSection);
  661. PeakDataCache.Empty();
  662. StoredWaveformDescriptor = AkAudioWaveformViewport::WaveformDescriptor();
  663. FTimeToPixel TempTimeToPixel(FGeometry(), TRange<double>(0.0f, 0.0f), FFrameRate());
  664. RegenerateWaveform(TempTimeToPixel, StoredWaveformDescriptor);
  665. }
  666. }
  667. }
  668. else
  669. {
  670. Section->Initialize();
  671. }
  672. }
  673. else
  674. {
  675. WaveformViewport.Reset();
  676. StoredWaveformDescriptor = AkAudioWaveformViewport::WaveformDescriptor();
  677. }
  678. }
  679. void ResumeWaveformUpdates()
  680. {
  681. Section->UpdateAudioSourceInfo();
  682. StoredEventName = Section->GetEventName();
  683. WaapiReconnected = true;
  684. ShouldUpdateWaveform = true;
  685. }
  686. void StopWaveformUpdates()
  687. {
  688. ShouldUpdateWaveform = false;
  689. Section->InvalidateAudioSourceInfo();
  690. }
  691. };
  692. FMovieSceneAkAudioEventTrackEditor::FMovieSceneAkAudioEventTrackEditor(TSharedRef<ISequencer> InSequencer)
  693. : FMovieSceneTrackEditor(InSequencer)
  694. {
  695. }
  696. TSharedRef<ISequencerTrackEditor> FMovieSceneAkAudioEventTrackEditor::CreateTrackEditor(TSharedRef<ISequencer> InSequencer)
  697. {
  698. return MakeShareable(new FMovieSceneAkAudioEventTrackEditor(InSequencer));
  699. }
  700. bool FMovieSceneAkAudioEventTrackEditor::SupportsType(TSubclassOf<UMovieSceneTrack> Type) const
  701. {
  702. return Type == UMovieSceneAkAudioEventTrack::StaticClass();
  703. }
  704. bool FMovieSceneAkAudioEventTrackEditor::SupportsSequence(UMovieSceneSequence* InSequence) const
  705. {
  706. #if UE_5_1_OR_LATER
  707. static UClass* LevelSequenceClass = UClass::TryFindTypeSlow<UClass>(TEXT("/Script/LevelSequence.LevelSequence"), EFindFirstObjectOptions::ExactClass);
  708. #else
  709. static UClass* LevelSequenceClass = FindObject<UClass>(ANY_PACKAGE, TEXT("LevelSequence"), true);
  710. #endif
  711. return InSequence != nullptr && LevelSequenceClass != nullptr && InSequence->GetClass()->IsChildOf(LevelSequenceClass);
  712. }
  713. void FMovieSceneAkAudioEventTrackEditor::BuildTrackContextMenu(FMenuBuilder& MenuBuilder, UMovieSceneTrack* Track)
  714. {
  715. auto AkAudioEventTrack = Cast<UMovieSceneAkAudioEventTrack>(Track);
  716. MenuBuilder.BeginSection("Audiokinetic", LOCTEXT("AKMenu", "Audiokinetic"));
  717. {
  718. MenuBuilder.AddMenuEntry(
  719. LOCTEXT("RefreshAllWaveforms", "Save Wwise project and refresh all sections"),
  720. LOCTEXT("RefreshAllWaveformsTooltip", "Saves the Wwise project, generates required soundbanks for all sections and refreshes all waveforms."),
  721. FSlateIcon(),
  722. FUIAction(
  723. FExecuteAction::CreateLambda([=] { CreateGenerateSoundBanksWindowForAllSections(Track); }),
  724. FCanExecuteAction::CreateLambda([=]
  725. {
  726. auto aSections = Track->GetAllSections();
  727. if (aSections.Num() <= 0)
  728. return false;
  729. for (auto pSection : aSections)
  730. {
  731. UMovieSceneAkAudioEventSection* pAkEventSection = dynamic_cast<UMovieSceneAkAudioEventSection*>(pSection);
  732. if (pAkEventSection != nullptr)
  733. {
  734. return true;
  735. }
  736. }
  737. return false;
  738. })
  739. )
  740. );
  741. }
  742. MenuBuilder.EndSection();//Audiokinetic
  743. }
  744. /** Creates a soundbank generation window.
  745. */
  746. void FMovieSceneAkAudioEventTrackEditor::CreateGenerateSoundBanksWindowForAllSections(UMovieSceneTrack* in_pTrack)
  747. {
  748. UMovieSceneAkAudioEventTrack* pAkEventTrack = dynamic_cast<UMovieSceneAkAudioEventTrack*>(in_pTrack);
  749. if (pAkEventTrack != nullptr)
  750. {
  751. AkAudioBankGenerationHelper::CreateGenerateSoundDataWindow(true);
  752. }
  753. }
  754. TSharedRef<ISequencerSection> FMovieSceneAkAudioEventTrackEditor::MakeSectionInterface(UMovieSceneSection& SectionObject, UMovieSceneTrack& Track, FGuid ObjectBinding)
  755. {
  756. return MakeShareable(new FMovieSceneAkAudioEventSection(SectionObject, GetSequencer()));
  757. }
  758. bool FMovieSceneAkAudioEventTrackEditor::HandleAssetAdded(UObject* Asset, const FGuid& TargetObjectGuid)
  759. {
  760. if (Asset->IsA<UAkAudioEvent>())
  761. {
  762. if (!SupportsSequence(GetMovieSceneSequence()))
  763. {
  764. UE_LOG(LogAudiokineticTools, Warning, TEXT("AkAudioEventTrack only supports Level Sequences"));
  765. return false;
  766. }
  767. auto Event = Cast<UAkAudioEvent>(Asset);
  768. if (!Event->IsDataFullyLoaded())
  769. {
  770. Event->LoadEventDataForContentBrowserPreview();
  771. }
  772. if (TargetObjectGuid.IsValid())
  773. {
  774. TArray<TWeakObjectPtr<UObject>> ObjectsToAttachTo;
  775. for (TWeakObjectPtr<> Object : GetSequencer()->FindObjectsInCurrentSequence(TargetObjectGuid))
  776. ObjectsToAttachTo.Add(Object);
  777. auto AddNewAttachedSound = [Event, ObjectsToAttachTo, this](FFrameNumber KeyTime)
  778. {
  779. bool bHandleCreated = false;
  780. bool bTrackCreated = false;
  781. bool bTrackModified = false;
  782. for (int32 ObjectIndex = 0; ObjectIndex < ObjectsToAttachTo.Num(); ++ObjectIndex)
  783. {
  784. UObject* Object = ObjectsToAttachTo[ObjectIndex].Get();
  785. FFindOrCreateHandleResult HandleResult = FindOrCreateHandleToObject(Object);
  786. FGuid ObjectHandle = HandleResult.Handle;
  787. bHandleCreated |= HandleResult.bWasCreated;
  788. if (ObjectHandle.IsValid())
  789. {
  790. FFindOrCreateTrackResult TrackResult = FindOrCreateTrackForObject(ObjectHandle, UMovieSceneAkAudioEventTrack::StaticClass());
  791. bTrackCreated |= TrackResult.bWasCreated;
  792. if (ensure(TrackResult.Track))
  793. {
  794. auto AudioTrack = Cast<UMovieSceneAkAudioEventTrack>(TrackResult.Track);
  795. AudioTrack->AddNewEvent(KeyTime, Event);
  796. bTrackModified = true;
  797. }
  798. }
  799. }
  800. FKeyPropertyResult Result;
  801. Result.bTrackModified = bTrackModified;
  802. Result.bHandleCreated = bHandleCreated;
  803. Result.bTrackCreated = bTrackCreated;
  804. return Result;
  805. };
  806. AnimatablePropertyChanged(FOnKeyProperty::CreateLambda(AddNewAttachedSound));
  807. }
  808. else
  809. {
  810. auto AddNewMasterSound = [Event, this](FFrameNumber KeyTime)
  811. {
  812. #if UE_5_2_OR_LATER
  813. auto TrackResult = FindOrCreateRootTrack<UMovieSceneAkAudioEventTrack>();
  814. #else
  815. auto TrackResult = FindOrCreateMasterTrack<UMovieSceneAkAudioEventTrack>();
  816. #endif
  817. UMovieSceneTrack* Track = TrackResult.Track;
  818. auto AkEventTrack = Cast<UMovieSceneAkAudioEventTrack>(Track);
  819. AkEventTrack->AddNewEvent(KeyTime, Event);
  820. FKeyPropertyResult Result;
  821. Result.bTrackCreated = TrackResult.bWasCreated;
  822. Result.bTrackModified = true;
  823. return Result;
  824. };
  825. AnimatablePropertyChanged(FOnKeyProperty::CreateLambda(AddNewMasterSound));
  826. }
  827. return true;
  828. }
  829. return false;
  830. }
  831. const FSlateBrush* FMovieSceneAkAudioEventTrackEditor::GetIconBrush() const
  832. {
  833. return FAkAudioStyle::Get().GetBrush("AudiokineticTools.EventIcon");
  834. }
  835. void FMovieSceneAkAudioEventTrackEditor::BuildAddTrackMenu(FMenuBuilder& MenuBuilder)
  836. {
  837. MenuBuilder.AddMenuEntry(
  838. LOCTEXT("AddAkAudioEventTrack", "AkAudioEvent"),
  839. LOCTEXT("AddAkAudioEventMasterTrackTooltip", "Adds a master AkAudioEvent track."),
  840. FSlateIcon(FAkAudioStyle::GetStyleSetName(), "AudiokineticTools.EventIcon"),
  841. FUIAction(FExecuteAction::CreateLambda([this]
  842. {
  843. auto FocusedMovieScene = GetFocusedMovieScene();
  844. if (FocusedMovieScene == nullptr)
  845. {
  846. return;
  847. }
  848. const FScopedTransaction Transaction(LOCTEXT("AddAkAudioEventMasterTrack_Transaction", "Add master AkAudioEvent Track"));
  849. FocusedMovieScene->Modify();
  850. #if UE_5_2_OR_LATER
  851. auto NewTrack = FocusedMovieScene->AddTrack<UMovieSceneAkAudioEventTrack>();
  852. #else
  853. auto NewTrack = FocusedMovieScene->AddMasterTrack<UMovieSceneAkAudioEventTrack>();
  854. #endif
  855. ensure(NewTrack);
  856. NewTrack->SetIsAMasterTrack(true);
  857. GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
  858. }))
  859. );
  860. }
  861. TSharedPtr<SWidget> FMovieSceneAkAudioEventTrackEditor::BuildOutlinerEditWidget(const FGuid& ObjectBinding, UMovieSceneTrack* Track, const FBuildEditWidgetParams& Params)
  862. {
  863. // Create a container edit box
  864. return SNew(SHorizontalBox)
  865. // Add the audio combo box
  866. + SHorizontalBox::Slot()
  867. .AutoWidth()
  868. .VAlign(VAlign_Center)
  869. [
  870. FSequencerUtilities::MakeAddButton(LOCTEXT("AudioText", "AkAudioEvent"), FOnGetContent::CreateSP(this, &FMovieSceneAkAudioEventTrackEditor::BuildAudioSubMenu, Track), Params.NodeIsHovered, GetSequencer())
  871. ];
  872. }
  873. TSharedRef<SWidget> FMovieSceneAkAudioEventTrackEditor::BuildAudioSubMenu(UMovieSceneTrack* Track)
  874. {
  875. static const FName AssetRegistryModuleName = TEXT("AssetRegistry");
  876. FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryModuleName);
  877. #if UE_5_1_OR_LATER
  878. TArray<FTopLevelAssetPath> ClassPaths;
  879. ClassPaths.Add(UAkAudioEvent::StaticClass()->GetClassPathName());
  880. TSet<FTopLevelAssetPath> DerivedClassPaths;
  881. AssetRegistryModule.Get().GetDerivedClassNames(ClassPaths, {}, DerivedClassPaths);
  882. #else
  883. TArray<FName> ClassNames;
  884. ClassNames.Add(UAkAudioEvent::StaticClass()->GetFName());
  885. TSet<FName> DerivedClassNames;
  886. AssetRegistryModule.Get().GetDerivedClassNames(ClassNames, {}, DerivedClassNames);
  887. #endif
  888. FMenuBuilder MenuBuilder(true, nullptr);
  889. FAssetPickerConfig AssetPickerConfig;
  890. {
  891. AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateRaw(this, &FMovieSceneAkAudioEventTrackEditor::OnAudioAssetSelected, Track);
  892. AssetPickerConfig.bAllowNullSelection = true;
  893. AssetPickerConfig.InitialAssetViewType = EAssetViewType::List;
  894. #if UE_5_1_OR_LATER
  895. for (auto ClassPath : DerivedClassPaths)
  896. {
  897. AssetPickerConfig.Filter.ClassPaths.Add(ClassPath);
  898. }
  899. #else
  900. for (auto ClassName : DerivedClassNames)
  901. {
  902. AssetPickerConfig.Filter.ClassNames.Add(ClassName);
  903. }
  904. #endif
  905. }
  906. FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
  907. TSharedPtr<SBox> MenuEntry = SNew(SBox)
  908. .WidthOverride(300.0f)
  909. .HeightOverride(300.f)
  910. [
  911. ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig)
  912. ];
  913. MenuBuilder.AddWidget(MenuEntry.ToSharedRef(), FText::GetEmpty(), true);
  914. return MenuBuilder.MakeWidget();
  915. }
  916. void FMovieSceneAkAudioEventTrackEditor::OnAudioAssetSelected(const FAssetData& AssetData, UMovieSceneTrack* Track)
  917. {
  918. FSlateApplication::Get().DismissAllMenus();
  919. const FScopedTransaction Transaction(LOCTEXT("AddAkAudioEvent_Transaction", "Add AkAudioEvent"));
  920. Track->Modify();
  921. FFrameNumber KeyTime = GetSequencer()->GetGlobalTime().Time.FrameNumber;
  922. auto AudioTrack = Cast<UMovieSceneAkAudioEventTrack>(Track);
  923. UAkAudioEvent* NewEvent = Cast<UAkAudioEvent>(AssetData.GetAsset());
  924. AudioTrack->AddNewEvent(KeyTime, NewEvent);
  925. GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
  926. }
  927. void FMovieSceneAkAudioEventTrackEditor::BuildObjectBindingTrackMenu(FMenuBuilder& MenuBuilder, const TArray<FGuid>& ObjectBindings, const UClass* ObjectClass)
  928. {
  929. auto ObjectBinding = ObjectBindings[0];
  930. if (ObjectClass->IsChildOf(AActor::StaticClass()) || ObjectClass->IsChildOf(USceneComponent::StaticClass()))
  931. {
  932. MenuBuilder.AddMenuEntry(
  933. LOCTEXT("AddAkAudioEventTrack", "AkAudioEvent"),
  934. LOCTEXT("AddAkAudioEventTrackTooltip", "Adds an AkAudioEvent track."),
  935. FSlateIcon(FAkAudioStyle::GetStyleSetName(), "AudiokineticTools.EventIcon"),
  936. FUIAction(FExecuteAction::CreateLambda([this, ObjectBinding = MoveTemp(ObjectBinding)]
  937. {
  938. auto FocusedMovieScene = GetFocusedMovieScene();
  939. if (FocusedMovieScene == nullptr)
  940. {
  941. return;
  942. }
  943. const FScopedTransaction Transaction(LOCTEXT("AddAkAudioEventTrack_Transaction", "Add AkAudioEvent Track"));
  944. FocusedMovieScene->Modify();
  945. auto NewTrack = FocusedMovieScene->AddTrack<UMovieSceneAkAudioEventTrack>(ObjectBinding);
  946. ensure(NewTrack);
  947. GetSequencer()->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
  948. }))
  949. );
  950. }
  951. }
  952. #undef LOCTEXT_NAMESPACE