A modded EditSaber for making beat saber maps.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

662 lines
19 KiB

  1. // Fill out your copyright notice in the Description page of Project Settings.
  2. #include "eXiSoundVisPrivatePCH.h"
  3. #include "SoundVisComponent.h"
  4. #include "Sound/SoundWave.h"
  5. #include "AudioDevice.h"
  6. #include "Runtime/Engine/Public/VorbisAudioInfo.h"
  7. #include "Developer/TargetPlatform/Public/Interfaces/IAudioFormat.h"
  8. /// De-/Constructors
  9. USoundVisComponent::USoundVisComponent()
  10. {
  11. AudioComponent = CreateDefaultSubobject<UAudioComponent>(FName("AudioComponent"));
  12. PrimaryComponentTick.bCanEverTick = true;
  13. }
  14. USoundVisComponent::~USoundVisComponent()
  15. {
  16. }
  17. void USoundVisComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction * ThisTickFunction)
  18. {
  19. UGameViewportClient* Viewport = GetWorld()->GetGameViewport();
  20. if (bSoundPaused && bSoundPausedByBackgroundWindow)
  21. {
  22. if (Viewport->Viewport->IsForegroundWindow())
  23. {
  24. PrintLog(TEXT("Window is in foreground. Resuming!"));
  25. BP_ResumeCalculatingFrequencySpectrum();
  26. bSoundPausedByBackgroundWindow = false;
  27. }
  28. }
  29. }
  30. /// Functions to load Data from the HardDrive
  31. bool USoundVisComponent::LoadSoundFileFromHD(const FString& InFilePath)
  32. {
  33. // Create new SoundWave Object
  34. CompressedSoundWaveRef = NewObject<USoundWave>(USoundWave::StaticClass());
  35. // Make sure the SoundWave Object is Valid
  36. if (!CompressedSoundWaveRef) {
  37. PrintError(TEXT("Failed to create new SoundWave Object!"));
  38. return false;
  39. }
  40. // If true, the Sound was successfully loaded
  41. bool bLoaded = false;
  42. // TArray that holds the binary and encoded Sound data
  43. TArray<uint8> RawFile;
  44. // Load file into RawFile Array
  45. bLoaded = FFileHelper::LoadFileToArray(RawFile, InFilePath.GetCharArray().GetData());
  46. if (bLoaded)
  47. {
  48. UE_LOG(LogTemp, Error, TEXT("LoadSoundFileFromHD 0"));
  49. // Fill the SoundData into the SoundWave Object
  50. if (RawFile.Num() > 0) {
  51. bLoaded = FillSoundWaveInfo(CompressedSoundWaveRef, &RawFile);
  52. }
  53. else {
  54. PrintError(TEXT("RawFile Array is empty! Seams like Sound couldn't be loaded correctly."));
  55. bLoaded = false;
  56. }
  57. // Get Pointer to the Compressed OGG Data
  58. FByteBulkData* BulkData = &CompressedSoundWaveRef->CompressedFormatData.GetFormat(FName("OGG"));
  59. // Set the Lock of the BulkData to ReadWrite
  60. BulkData->Lock(LOCK_READ_WRITE);
  61. // Copy compressed RawFile Data to the Address of the OGG Data of the SW File
  62. FMemory::Memmove(BulkData->Realloc(RawFile.Num()), RawFile.GetData(), RawFile.Num());
  63. // Unlock the BulkData again
  64. BulkData->Unlock();
  65. }
  66. if (!bLoaded) {
  67. PrintError(TEXT("Something went wrong while loading the Sound Data!"));
  68. return false;
  69. }
  70. // Fill the PCMSampleBuffer
  71. return GetPCMDataFromFile(CompressedSoundWaveRef);
  72. }
  73. bool USoundVisComponent::FillSoundWaveInfo(USoundWave* InSoundWave, TArray<uint8>* InRawFile)
  74. {
  75. // Info Structs
  76. FSoundQualityInfo SoundQualityInfo;
  77. FVorbisAudioInfo VorbisAudioInfo;
  78. // Save the Info into SoundQualityInfo
  79. if (!VorbisAudioInfo.ReadCompressedInfo(InRawFile->GetData(), InRawFile->Num(), &SoundQualityInfo))
  80. {
  81. return false;
  82. }
  83. // Fill in all the Data we have
  84. InSoundWave->DecompressionType = EDecompressionType::DTYPE_RealTime;
  85. InSoundWave->SoundGroup = ESoundGroup::SOUNDGROUP_Default;
  86. InSoundWave->NumChannels = SoundQualityInfo.NumChannels;
  87. InSoundWave->Duration = SoundQualityInfo.Duration;
  88. InSoundWave->RawPCMDataSize = SoundQualityInfo.SampleDataSize;
  89. InSoundWave->SampleRate = SoundQualityInfo.SampleRate;
  90. return true;
  91. }
  92. /// Function to decompress the compressed Data that comes with the .ogg file
  93. bool USoundVisComponent::GetPCMDataFromFile(USoundWave* InSoundWave)
  94. {
  95. if (InSoundWave == nullptr) {
  96. PrintError(TEXT("Passed SoundWave pointer is a nullptr!"));
  97. return false;
  98. }
  99. if (InSoundWave->NumChannels < 1 || InSoundWave->NumChannels > 2) {
  100. PrintError(TEXT("SoundWave Object has not the right amount of Channels. Plugin only supports 1 or 2!"));
  101. return false;
  102. }
  103. if (GEngine)
  104. {
  105. // Get a Pointer to the Main Audio Device
  106. FAudioDevice* AudioDevice = GEngine->GetMainAudioDevice();
  107. if (AudioDevice) {
  108. InSoundWave->InitAudioResource(AudioDevice->GetRuntimeFormat(InSoundWave));
  109. PrintLog(TEXT("Creating new DecompressWorker."));
  110. // Creates a new DecompressWorker and starts it
  111. InitNewDecompressTask(InSoundWave);
  112. return true;
  113. }
  114. else {
  115. PrintError(TEXT("Couldn't get a valid Pointer to the Main AudioDevice!"));
  116. }
  117. }
  118. return false;
  119. }
  120. void USoundVisComponent::CalculateFrequencySpectrum(USoundWave* InSoundWaveRef, const float InStartTime, const float InDuration, TArray<float>& OutFrequencies)
  121. {
  122. // Clear the Array before continuing
  123. OutFrequencies.Empty();
  124. const int32 NumChannels = InSoundWaveRef->NumChannels;
  125. const int32 SampleRate = InSoundWaveRef->SampleRate;
  126. // Make sure the Number of Channels is correct
  127. if (NumChannels > 0 && NumChannels <= 2)
  128. {
  129. // Check if we actually have a Buffer to work with
  130. if (InSoundWaveRef->CachedRealtimeFirstBuffer)
  131. {
  132. // The first sample is just the StartTime * SampleRate
  133. int32 FirstSample = SampleRate * InStartTime;
  134. // The last sample is the SampleRate times (StartTime plus the Duration)
  135. int32 LastSample = SampleRate * (InStartTime + InDuration);
  136. // Get Maximum amount of samples in this Sound
  137. const int32 SampleCount = InSoundWaveRef->RawPCMDataSize / (2 * NumChannels);
  138. // An early check if we can create a Sample window
  139. FirstSample = FMath::Min(SampleCount, FirstSample);
  140. LastSample = FMath::Min(SampleCount, LastSample);
  141. // Actual amount of samples we gonna read
  142. int32 SamplesToRead = LastSample - FirstSample;
  143. if (SamplesToRead < 0) {
  144. PrintError(TEXT("Number of SamplesToRead is < 0!"));
  145. return;
  146. }
  147. // Shift the window enough so that we get a PowerOfTwo. FFT works better with that
  148. int32 PoT = 2;
  149. while (SamplesToRead > PoT) {
  150. PoT *= 2;
  151. }
  152. // Now we have a good PowerOfTwo to work with
  153. SamplesToRead = PoT;
  154. // Create two 2-dim Arrays for complex numbers | Buffer and Output
  155. kiss_fft_cpx* Buffer[2] = { 0 };
  156. kiss_fft_cpx* Output[2] = { 0 };
  157. // Create 1-dim Array with one slot for SamplesToRead
  158. int32 Dims[1] = { SamplesToRead };
  159. kiss_fftnd_cfg STF = kiss_fftnd_alloc(Dims, 1, 0, nullptr, nullptr);
  160. int16* SamplePtr = reinterpret_cast<int16*>(InSoundWaveRef->CachedRealtimeFirstBuffer);
  161. // Allocate space in the Buffer and Output Arrays for all the data that FFT returns
  162. for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ChannelIndex++)
  163. {
  164. Buffer[ChannelIndex] = (kiss_fft_cpx*)KISS_FFT_MALLOC(sizeof(kiss_fft_cpx) * SamplesToRead);
  165. Output[ChannelIndex] = (kiss_fft_cpx*)KISS_FFT_MALLOC(sizeof(kiss_fft_cpx) * SamplesToRead);
  166. }
  167. // Shift our SamplePointer to the Current "FirstSample"
  168. SamplePtr += FirstSample * NumChannels;
  169. for (int32 SampleIndex = 0; SampleIndex < SamplesToRead; SampleIndex++)
  170. {
  171. for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ChannelIndex++)
  172. {
  173. // Make sure the Point is Valid and we don't go out of bounds
  174. if (SamplePtr != NULL && (SampleIndex + FirstSample < SampleCount))
  175. {
  176. // Use Window function to get a better result for the Data (Hann Window)
  177. Buffer[ChannelIndex][SampleIndex].r = GetFFTInValue(*SamplePtr, SampleIndex, SamplesToRead);
  178. Buffer[ChannelIndex][SampleIndex].i = 0.f;
  179. }
  180. else
  181. {
  182. // Use Window function to get a better result for the Data (Hann Window)
  183. Buffer[ChannelIndex][SampleIndex].r = 0.f;
  184. Buffer[ChannelIndex][SampleIndex].i = 0.f;
  185. }
  186. // Take the next Sample
  187. SamplePtr++;
  188. }
  189. }
  190. // Now that the Buffer is filled, use the FFT
  191. for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ChannelIndex++)
  192. {
  193. if (Buffer[ChannelIndex])
  194. {
  195. kiss_fftnd(STF, Buffer[ChannelIndex], Output[ChannelIndex]);
  196. }
  197. }
  198. OutFrequencies.AddZeroed(SamplesToRead);
  199. for (int32 SampleIndex = 0; SampleIndex < SamplesToRead; ++SampleIndex)
  200. {
  201. float ChannelSum = 0.0f;
  202. for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ++ChannelIndex)
  203. {
  204. if (Output[ChannelIndex])
  205. {
  206. // With this we get the actual Frequency value for the frequencies from 0hz to ~22000hz
  207. ChannelSum += FMath::Sqrt(FMath::Square(Output[ChannelIndex][SampleIndex].r) + FMath::Square(Output[ChannelIndex][SampleIndex].i));
  208. }
  209. }
  210. if (bNormalizeOutputToDb)
  211. {
  212. OutFrequencies[SampleIndex] = FMath::LogX(10, ChannelSum / NumChannels) * 10;
  213. }
  214. else
  215. {
  216. OutFrequencies[SampleIndex] = ChannelSum / NumChannels;
  217. }
  218. }
  219. // Make sure to free up the FFT stuff
  220. KISS_FFT_FREE(STF);
  221. for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ++ChannelIndex)
  222. {
  223. KISS_FFT_FREE(Buffer[ChannelIndex]);
  224. KISS_FFT_FREE(Output[ChannelIndex]);
  225. }
  226. }
  227. else {
  228. PrintError(TEXT("InSoundVisData.PCMData is a nullptr!"));
  229. }
  230. }
  231. else {
  232. PrintError(TEXT("Number of Channels is < 0!"));
  233. }
  234. }
  235. float USoundVisComponent::GetFFTInValue(const int16 InSampleValue, const int16 InSampleIndex, const int16 InSampleCount)
  236. {
  237. float FFTValue = InSampleValue;
  238. // Apply the Hann window
  239. FFTValue *= 0.5f * (1 - FMath::Cos(2 * PI * InSampleIndex / (InSampleCount - 1)));
  240. return FFTValue;
  241. }
  242. void USoundVisComponent::InitNewDecompressTask(USoundWave* InSoundWaveRef)
  243. {
  244. // Do we already have a valid Runnable? If not, create a new one
  245. if (FAudioDecompressWorker::Runnable == NULL)
  246. {
  247. // Start Timer that watches the DecompressWorker State
  248. GetWorld()->GetTimerManager().ClearTimer(AudioDecompressTimer);
  249. GetWorld()->GetTimerManager().SetTimer(AudioDecompressTimer, this, &USoundVisComponent::Notify_SoundDecompressed, 0.1f, true);
  250. // Init new Worker and pass the SoundWaveRef to decompress it
  251. FAudioDecompressWorker::Runnable->InitializeWorker(InSoundWaveRef);
  252. }
  253. else if(FAudioDecompressWorker::Runnable->IsFinished())
  254. {
  255. // The Worker is finished and still valid, shut it down!
  256. FAudioDecompressWorker::ShutdownWorker();
  257. // Start Timer that watches the DecompressWorker State
  258. GetWorld()->GetTimerManager().ClearTimer(AudioDecompressTimer);
  259. GetWorld()->GetTimerManager().SetTimer(AudioDecompressTimer, this, &USoundVisComponent::Notify_SoundDecompressed, 0.1f, true);
  260. // Init new Worker and pass the SoundWaveRef to decompress it
  261. FAudioDecompressWorker::Runnable->InitializeWorker(InSoundWaveRef);
  262. }
  263. else {
  264. PrintLog(TEXT("Worker not finished!"));
  265. }
  266. }
  267. void USoundVisComponent::Notify_SoundDecompressed()
  268. {
  269. // If the Worker finished..
  270. if (FAudioDecompressWorker::Runnable->IsFinished())
  271. {
  272. // ..clear the timer and..
  273. GetWorld()->GetTimerManager().ClearTimer(AudioDecompressTimer);
  274. try {
  275. //..broadcast the result to the Blueprint
  276. OnFileLoadCompleted.Broadcast(FAudioDecompressWorker::Runnable->GetSoundWaveRef());
  277. } catch (const std::exception&) {
  278. // Yo, bad things happened
  279. }
  280. PrintLog(TEXT("Worker finished!"));
  281. }
  282. else {
  283. PrintLog(TEXT("Worker is working!"));
  284. }
  285. }
  286. void USoundVisComponent::HandleFrequencySpectrumCalculation()
  287. {
  288. // Reference to the Client Viewport
  289. UGameViewportClient* Viewport = GetWorld()->GetGameViewport();
  290. // If the Window is not in the foreground, make sure to pause everything, so it won't get async!
  291. if (!Viewport->Viewport->IsForegroundWindow() && bPauseWhenWindowInBackground)
  292. {
  293. PrintLog(TEXT("Window is not in foreground. Pausing!"));
  294. BP_PauseCalculatingFrequencySpectrum();
  295. bSoundPausedByBackgroundWindow = true;
  296. }
  297. // Only proceed if we are not over the duration, or stopped/pause calculating
  298. if (CurrentSoundData && GetWorld()->GetTimerManager().GetTimerElapsed(SoundPlayerTimer) <= CurrentSoundData->Duration && bSoundPlaying)
  299. {
  300. // Dummy Array to Store the Frequencies in
  301. TArray<float> OutFrequencies;
  302. // Let our Function Calculate the Frequency Spectrum
  303. CalculateFrequencySpectrum(CurrentSoundData, GetWorld()->GetTimerManager().GetTimerElapsed(SoundPlayerTimer), CurrentSegmentLength, OutFrequencies);
  304. // Now that this is done, call the Delegate, so the Blueprint can work with it
  305. OnFrequencySpectrumCalculated.Broadcast(OutFrequencies);
  306. // Start this function again after a short delay
  307. FrequencySpectrumTimerDelegate.BindUFunction(this, FName("HandleFrequencySpectrumCalculation"));
  308. GetWorld()->GetTimerManager().SetTimer(FrequencySpectrumTimer, FrequencySpectrumTimerDelegate, 0.01f, false);
  309. }
  310. else
  311. {
  312. GetWorld()->GetTimerManager().ClearTimer(FrequencySpectrumTimer);
  313. }
  314. }
  315. /// Blueprint Versions of the File Data Functions
  316. bool USoundVisComponent::BP_LoadSoundFileFromHD(const FString InFilePath)
  317. {
  318. return LoadSoundFileFromHD(InFilePath);
  319. }
  320. void USoundVisComponent::BP_LoadAllSoundFileNamesFromHD(bool& bLoaded, const FString InDirectoryPath, const bool bInAbsolutePath, const FString InFileExtension, TArray<FString>& OutSoundFileNamesWithPath, TArray<FString>& OutSoundFileNamesWithoutPath)
  321. {
  322. FString FinalPath = InDirectoryPath;
  323. if (!bInAbsolutePath)
  324. {
  325. FinalPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()) + InDirectoryPath;
  326. }
  327. TArray<FString> DirectoriesToSkip;
  328. IPlatformFile &PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
  329. FLocalTimestampDirectoryVisitor Visitor(PlatformFile, DirectoriesToSkip, DirectoriesToSkip, false);
  330. PlatformFile.IterateDirectory(*FinalPath, Visitor);
  331. for (TMap<FString, FDateTime>::TIterator TimestampIt(Visitor.FileTimes); TimestampIt; ++TimestampIt)
  332. {
  333. const FString FilePath = TimestampIt.Key();
  334. FString FileName = FPaths::GetCleanFilename(FilePath);
  335. bool bShouldAddFile = true;
  336. if (!InFileExtension.IsEmpty())
  337. {
  338. if (!(FPaths::GetExtension(FileName, false).Equals(InFileExtension, ESearchCase::IgnoreCase)))
  339. {
  340. bShouldAddFile = false;
  341. }
  342. }
  343. if (bShouldAddFile)
  344. {
  345. OutSoundFileNamesWithPath.Add(FilePath);
  346. FileName.FString::Split(FString("."), &FileName, nullptr, ESearchCase::IgnoreCase);
  347. OutSoundFileNamesWithoutPath.Add(FileName);
  348. }
  349. }
  350. bLoaded = true;
  351. }
  352. /// Blueprint Versions of the Analyze Functions
  353. void USoundVisComponent::BP_CalculateFrequencySpectrum(USoundWave* InSoundWaveRef, const float InStartTime, const float InDuration, TArray<float>& OutFrequencies)
  354. {
  355. CalculateFrequencySpectrum(InSoundWaveRef, InStartTime, InDuration, OutFrequencies);
  356. }
  357. void USoundVisComponent::BP_StartCalculatingFrequencySpectrum(USoundWave* InSoundWaveRef, const float InSegmentLength)
  358. {
  359. // When the Sound Ref is NULL, better not start analyzing
  360. if (InSoundWaveRef == nullptr) {
  361. PrintError(TEXT("SoundWaveRef is a nullptr. Please load a Sound first!"));
  362. return;
  363. }
  364. // Make sure we are not already playing something
  365. if (!bSoundPlaying)
  366. {
  367. // If we pause, make sure to stop the Player first
  368. if (GetWorld()->GetTimerManager().IsTimerPaused(SoundPlayerTimer))
  369. {
  370. BP_StopCalculatingFrequencySpectrum();
  371. }
  372. // Save the Current SoundVisData
  373. CurrentSoundData = InSoundWaveRef;
  374. // And the Current SegmentLength
  375. CurrentSegmentLength = InSegmentLength;
  376. // Set the Sound and Play it
  377. AudioComponent->SetSound(InSoundWaveRef);
  378. AudioComponent->Play();
  379. // Mark Sound as playing
  380. bSoundPlaying = true;
  381. // Start Timer that is way too long, so we can use it as some kind of AudioPlayer Timer
  382. GetWorld()->GetTimerManager().SetTimer(SoundPlayerTimer, 99999.f, false);
  383. // Start the Calculation Loop
  384. HandleFrequencySpectrumCalculation();
  385. }
  386. else {
  387. PrintWarning(TEXT("AudioComponent is already Playing. Please stop it first!"));
  388. }
  389. }
  390. void USoundVisComponent::BP_PauseCalculatingFrequencySpectrum()
  391. {
  392. // We can only pause, if we are playing
  393. if (bSoundPlaying && !bSoundPaused)
  394. {
  395. // Stop the Audio Component
  396. AudioComponent->Stop();
  397. // Mark sound as being paused
  398. bSoundPaused = true;
  399. // Pause the timer
  400. GetWorld()->GetTimerManager().PauseTimer(SoundPlayerTimer);
  401. // Start the tick, so we can check when the Player is back in the game
  402. SetComponentTickEnabled(true);
  403. }
  404. else {
  405. PrintWarning(TEXT("You can't pause something, that is not playing!"));
  406. }
  407. }
  408. void USoundVisComponent::BP_StopCalculatingFrequencySpectrum()
  409. {
  410. // If we are playing the Sound, or it's paused, stop it
  411. if (bSoundPlaying || bSoundPaused)
  412. {
  413. // Stop the AudioComponent
  414. AudioComponent->Stop();
  415. // Mark Sound as not playing or paused
  416. bSoundPaused = false;
  417. bSoundPlaying = false;
  418. bSoundPausedByBackgroundWindow = false;
  419. // Clear the timer
  420. GetWorld()->GetTimerManager().ClearTimer(SoundPlayerTimer);
  421. // Clear Current SoundVisData
  422. CurrentSoundData = nullptr;
  423. // Clear CurrentSegmentLength
  424. CurrentSegmentLength = 0.0f;
  425. // Stop the Tick Function
  426. SetComponentTickEnabled(false);
  427. }
  428. else {
  429. PrintWarning(TEXT("You can't stop something, that is not playing or paused!"));
  430. }
  431. }
  432. void USoundVisComponent::BP_ResumeCalculatingFrequencySpectrum()
  433. {
  434. if (bSoundPlaying && bSoundPaused)
  435. {
  436. // Start the Sound at the Point where we left it
  437. AudioComponent->Play(GetWorld()->GetTimerManager().GetTimerElapsed(SoundPlayerTimer));
  438. // Mark sound as no longer paused
  439. bSoundPaused = false;
  440. // UnPause the Timer again
  441. GetWorld()->GetTimerManager().UnPauseTimer(SoundPlayerTimer);
  442. // And start the Calculation again
  443. HandleFrequencySpectrumCalculation();
  444. // Stop the Tick Function
  445. SetComponentTickEnabled(false);
  446. }
  447. else {
  448. PrintWarning(TEXT("AudioComponent is Playing or not paused!"));
  449. }
  450. }
  451. bool USoundVisComponent::IsPlayerPlaying()
  452. {
  453. return (bSoundPlaying && !bSoundPaused);
  454. }
  455. bool USoundVisComponent::IsPlayerPaused()
  456. {
  457. UWorld* World = GetWorld();
  458. if (World)
  459. {
  460. return (bSoundPlaying && bSoundPaused);
  461. }
  462. return false;
  463. }
  464. float USoundVisComponent::GetCurrentPlayBackTime()
  465. {
  466. UWorld* World = GetWorld();
  467. if (World)
  468. {
  469. return World->GetTimerManager().GetTimerElapsed(SoundPlayerTimer);
  470. }
  471. return 0.0f;
  472. }
  473. /// Frequency Data Functions
  474. void USoundVisComponent::BP_GetSpecificFrequencyValue(USoundWave* InSoundWave, TArray<float> InFrequencies, int32 InWantedFrequency, float& OutFrequencyValue)
  475. {
  476. if (InSoundWave && InFrequencies.Num() > 0 && (int32)(InWantedFrequency * InFrequencies.Num() * 2 / InSoundWave->SampleRate) < InFrequencies.Num())
  477. {
  478. OutFrequencyValue = InFrequencies[(int32)(InWantedFrequency * InFrequencies.Num() * 2 / InSoundWave->SampleRate)];
  479. }
  480. }
  481. void USoundVisComponent::BP_GetAverageSubBassValue(USoundWave* InSoundWave, TArray<float> InFrequencies, float& OutAverageSubBass)
  482. {
  483. BP_GetAverageFrequencyValueInRange(InSoundWave, InFrequencies, 20, 60, OutAverageSubBass);
  484. }
  485. void USoundVisComponent::BP_GetAverageBassValue(USoundWave* InSoundWave, TArray<float> InFrequencies, float& OutAverageBass)
  486. {
  487. if (InSoundWave)
  488. {
  489. BP_GetAverageFrequencyValueInRange(InSoundWave, InFrequencies, 60, 250, OutAverageBass);
  490. }
  491. }
  492. void USoundVisComponent::BP_GetAverageFrequencyValueInRange(USoundWave* InSoundWave, TArray<float> InFrequencies, int32 InStartFrequence, int32 InEndFrequence, float& OutAverageFrequency)
  493. {
  494. // Init the Return Value
  495. OutAverageFrequency = 0.0f;
  496. if (InSoundWave == nullptr)
  497. return;
  498. if (InStartFrequence >= InEndFrequence || InStartFrequence < 0 || InEndFrequence > 22000)
  499. return;
  500. int32 FStart = (int32)(InStartFrequence * InFrequencies.Num() * 2 / InSoundWave->SampleRate);
  501. int32 FEnd = (int32)(InEndFrequence * InFrequencies.Num() * 2 / InSoundWave->SampleRate);
  502. if (FStart < 0 || FEnd >= InFrequencies.Num())
  503. return;
  504. int32 NumberOfFrequencies = 0;
  505. float ValueSum = 0.0f;
  506. for (int i = FStart; i <= FEnd; i++)
  507. {
  508. NumberOfFrequencies++;
  509. ValueSum += InFrequencies[i];
  510. }
  511. OutAverageFrequency = ValueSum / NumberOfFrequencies;
  512. }