Initial Commit!

いきますよ!
This commit is contained in:
squeaksies
2018-10-03 21:11:00 -07:00
commit ad200d2930
283 changed files with 17366 additions and 0 deletions

17
Plugins/eXiSoundVis/.gitattributes vendored Normal file
View File

@@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

49
Plugins/eXiSoundVis/.gitignore vendored Normal file
View File

@@ -0,0 +1,49 @@
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
/Binaries
/Intermediate

View File

@@ -0,0 +1,123 @@
[![](http://imgur.com/DWGECXG.gif)](https://www.youtube.com/watch?v=N4eA68BEpak)
<sup>**Note:** The image above is an animated GIF, so there's some quality lost. Please see the [YouTube video](https://www.youtube.com/watch?v=N4eA68BEpak) for higher quality visualization of Disclosure's You and Me.</sup>
This is an Unreal Engine 4 plugin that loads `.ogg` files at runtime and analyzes them to get the frequency spectrum to control gameplay, visualization and more.
Feature list and pictures of available nodes:
---------------------------------------------
* Load `.ogg` file from HDD
* Load sound names and file paths from HDD to display list of sound
* Get frequency spectrum of a loaded Sound
* Get specific frequency values by using the calculated frequency spectrum
* Start/Pause/Resume/Stop your Sound and Frequency Calculation through the build in Player
![](http://puu.sh/opY1K/16c2b7b3c5.jpg)
![](http://puu.sh/opYtT/ed734b2396.png)
![](http://puu.sh/opYPr/e850f7baf0.jpg)
![](http://puu.sh/oq0nd/c72fb3d48e.jpg)
Installation
-------------
Unzip the package into the Plugins directory of your game.
To add it as an engine plugin you will need to unzip the module into the plugin directory under where you installed UE4.
**1.** Download the ZIP File.
**2.** Create a `Plugins` folder in your game or engine directory and extract the plugin into it. It should look something like this:
![](http://puu.sh/oqMnc/f1f3292bc0.png)
**3.** Open your project (and/or regenerate the Visual Studio files to have the plugin in your solution) and enable it in the plugin section:
![](http://puu.sh/oqM0q/9a07b082eb.png)
**4.** To use the Plugin, add the `SoundVisComponent` to the Actor of your choice, which comes with the plugin:
![](http://puu.sh/oqMuE/10892c5bc2.png)
**5.** Load a sound via its **ABSOLUTE** path (only `.ogg` files). The Component has a `Delegate | OnFileLoadCompleted`, which gets called and passes the complete `USoundWave` Reference, once the process is over (ASYNC)!:
![](http://puu.sh/oqN7E/72273737df.jpg)
**5.1** NEVER CLOSE THE PROJECT WHILE LOADING A SOUND!
**6.** Use the Calculate Frequency Spectrum function after you loaded a sound to get an `Array of Frequency Values`, which represents the Frequencies from 0 to ~22000hz:
![](http://puu.sh/oqNpa/8a3b11650c.jpg)
**6.1** THIS ONLY WORKS WITH LOADED AND DECOMPRESSED .ogg FILES!
**7.** Since this only analyzes one small segment of the Sound and we don't want ugly `Delay-Loops`, use these functions to Start/Pause/Resume/Stop the whole sound (will also play it!)
![](http://puu.sh/opYPr/e850f7baf0.jpg)
**7.1** The analyzed Frequency Spectrum will be returned via a second `Delegate | OnFrequencySpectrumCalculated`, which also comes with the Component.
![](http://puu.sh/opYVk/aaf6479271.jpg)
**8.** Now you can use the different frequency functions to get the values (for example if you want to get the values for bass, use 20 to 60 for SubBass and 60 to 250 for Bass. You can look up more on the internet.
![](http://puu.sh/opYtT/ed734b2396.png)
**11.** You can find all functions available by going to the SoundVis category. They are explained in the [`SoundVisComponent.h`](https://github.com/eXifreXi/eXiSoundVis/blob/4.11/Source/eXiSoundVis/Public/SoundVisComponent.h) if you don't know how to use them:
![](http://puu.sh/oqNUm/317177f03d.png)
When cooking, make sure to add the plugin to your projects dependencies!
![](http://i.imgur.com/fh8VB1T.png)
Example project
---------------
- 4.16 (No idea if properly commented or not and rather ugly, but maybe it helps!): http://www.cedric-neukirchen.net/Downloads/SoundVisDemo.7z
License
-------------
By using this plugin you accept the CC-BY 4.0 license and
The MIT License (MIT)
Copyright (c) 2015 Cedric Neukirchen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Contact
-------------
If you have any Questions, Comments, Bug reports or feature requests for this plugin,
or you wish to contact me you can:
email me - cedric.neukirchen@gmx.de
contact me on the forum - Username: eXi
contact me on the Discord Unreal-Slackers Server - Username: cedric_exi
Credits
--------------
n00854180t - Helping a lot to get the loading of the .ogg file running
moss - Helping to understand wave files

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,128 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "eXiSoundVisPrivatePCH.h"
#include "AudioDecompressWorker.h"
#include "SoundVisComponent.h"
#include "AudioDevice.h"
#include "Developer/TargetPlatform/Public/Interfaces/IAudioFormat.h"
FAudioDecompressWorker* FAudioDecompressWorker::Runnable = NULL;
int32 FAudioDecompressWorker::ThreadCounter = 0;
FAudioDecompressWorker::FAudioDecompressWorker(class USoundWave* InSoundWaveRef)
: SoundWaveRef(InSoundWaveRef)
, AudioInfo(NULL)
, Thread(NULL)
{
if (GEngine && GEngine->GetMainAudioDevice())
{
AudioInfo = GEngine->GetMainAudioDevice()->CreateCompressedAudioInfo(SoundWaveRef);
}
// Higher overall ThreadCounter to avoid duplicated names
FAudioDecompressWorker::ThreadCounter++;
Thread = FRunnableThread::Create(this, *FString::Printf(TEXT("FAudioDecompressWorker%d"), FAudioDecompressWorker::ThreadCounter), 0, EThreadPriority::TPri_Normal);
}
FAudioDecompressWorker::~FAudioDecompressWorker()
{
delete Thread;
Thread = NULL;
}
FAudioDecompressWorker* FAudioDecompressWorker::InitializeWorker(class USoundWave* InSoundWaveRef)
{
Runnable = new FAudioDecompressWorker(InSoundWaveRef);
return Runnable;
}
void FAudioDecompressWorker::ShutdownWorker()
{
if (Runnable)
{
Runnable->EnsureCompletion();
delete Runnable;
Runnable = NULL;
}
}
bool FAudioDecompressWorker::Init()
{
// Make sure the Worker is marked is not finished
bIsFinished = false;
return true;
}
uint32 FAudioDecompressWorker::Run()
{
if (!SoundWaveRef)
{
return 0;
}
if (AudioInfo != NULL)
{
FSoundQualityInfo QualityInfo = { 0 };
// Parse the audio header for the relevant information
if (!(SoundWaveRef->ResourceData))
{
return 0;
}
if (AudioInfo->ReadCompressedInfo(SoundWaveRef->ResourceData, SoundWaveRef->ResourceSize, &QualityInfo))
{
FScopeCycleCounterUObject WaveObject(SoundWaveRef);
// Extract the data
SoundWaveRef->SampleRate = QualityInfo.SampleRate;
SoundWaveRef->NumChannels = QualityInfo.NumChannels;
if (QualityInfo.Duration > 0.0f)
{
SoundWaveRef->Duration = QualityInfo.Duration;
}
const uint32 PCMBufferSize = SoundWaveRef->Duration * SoundWaveRef->SampleRate * SoundWaveRef->NumChannels;
SoundWaveRef->CachedRealtimeFirstBuffer = new uint8[PCMBufferSize * 2];
AudioInfo->SeekToTime(0.0f);
AudioInfo->ReadCompressedData(SoundWaveRef->CachedRealtimeFirstBuffer, false, PCMBufferSize * 2);
}
else if (SoundWaveRef->DecompressionType == DTYPE_RealTime || SoundWaveRef->DecompressionType == DTYPE_Native)
{
SoundWaveRef->RemoveAudioResource();
}
delete AudioInfo;
}
return 0;
}
void FAudioDecompressWorker::Stop()
{
StopTaskCounter.Increment();
}
void FAudioDecompressWorker::Exit()
{
// Make sure to mark Thread as finished
bIsFinished = true;
}
void FAudioDecompressWorker::EnsureCompletion()
{
Stop();
if (Thread != NULL) {
Thread->WaitForCompletion();
}
}

View File

@@ -0,0 +1,656 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "eXiSoundVisPrivatePCH.h"
#include "SoundVisComponent.h"
#include "Sound/SoundWave.h"
#include "AudioDevice.h"
#include "Runtime/Engine/Public/VorbisAudioInfo.h"
#include "Developer/TargetPlatform/Public/Interfaces/IAudioFormat.h"
/// De-/Constructors
USoundVisComponent::USoundVisComponent()
{
AudioComponent = CreateDefaultSubobject<UAudioComponent>(FName("AudioComponent"));
PrimaryComponentTick.bCanEverTick = true;
}
USoundVisComponent::~USoundVisComponent()
{
}
void USoundVisComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction * ThisTickFunction)
{
UGameViewportClient* Viewport = GetWorld()->GetGameViewport();
if (bSoundPaused && bSoundPausedByBackgroundWindow)
{
if (Viewport->Viewport->IsForegroundWindow())
{
PrintLog(TEXT("Window is in foreground. Resuming!"));
BP_ResumeCalculatingFrequencySpectrum();
bSoundPausedByBackgroundWindow = false;
}
}
}
/// Functions to load Data from the HardDrive
void USoundVisComponent::LoadSoundFileFromHD(const FString& InFilePath)
{
// Create new SoundWave Object
CompressedSoundWaveRef = NewObject<USoundWave>(USoundWave::StaticClass());
// Make sure the SoundWave Object is Valid
if (!CompressedSoundWaveRef) {
PrintError(TEXT("Failed to create new SoundWave Object!"));
return;
}
// If true, the Sound was successfully loaded
bool bLoaded = false;
// TArray that holds the binary and encoded Sound data
TArray<uint8> RawFile;
// Load file into RawFile Array
bLoaded = FFileHelper::LoadFileToArray(RawFile, InFilePath.GetCharArray().GetData());
if (bLoaded)
{
// Fill the SoundData into the SoundWave Object
if (RawFile.Num() > 0) {
bLoaded = FillSoundWaveInfo(CompressedSoundWaveRef, &RawFile);
}
else {
PrintError(TEXT("RawFile Array is empty! Seams like Sound couldn't be loaded correctly."));
bLoaded = false;
}
// Get Pointer to the Compressed OGG Data
FByteBulkData* BulkData = &CompressedSoundWaveRef->CompressedFormatData.GetFormat(FName("OGG"));
// Set the Lock of the BulkData to ReadWrite
BulkData->Lock(LOCK_READ_WRITE);
// Copy compressed RawFile Data to the Address of the OGG Data of the SW File
FMemory::Memmove(BulkData->Realloc(RawFile.Num()), RawFile.GetData(), RawFile.Num());
// Unlock the BulkData again
BulkData->Unlock();
}
if (!bLoaded) {
PrintError(TEXT("Something went wrong while loading the Sound Data!"));
return;
}
// Fill the PCMSampleBuffer
GetPCMDataFromFile(CompressedSoundWaveRef);
}
bool USoundVisComponent::FillSoundWaveInfo(USoundWave* InSoundWave, TArray<uint8>* InRawFile)
{
// Info Structs
FSoundQualityInfo SoundQualityInfo;
FVorbisAudioInfo VorbisAudioInfo;
// Save the Info into SoundQualityInfo
if (!VorbisAudioInfo.ReadCompressedInfo(InRawFile->GetData(), InRawFile->Num(), &SoundQualityInfo))
{
return false;
}
// Fill in all the Data we have
InSoundWave->DecompressionType = EDecompressionType::DTYPE_RealTime;
InSoundWave->SoundGroup = ESoundGroup::SOUNDGROUP_Default;
InSoundWave->NumChannels = SoundQualityInfo.NumChannels;
InSoundWave->Duration = SoundQualityInfo.Duration;
InSoundWave->RawPCMDataSize = SoundQualityInfo.SampleDataSize;
InSoundWave->SampleRate = SoundQualityInfo.SampleRate;
return true;
}
/// Function to decompress the compressed Data that comes with the .ogg file
void USoundVisComponent::GetPCMDataFromFile(USoundWave* InSoundWave)
{
if (InSoundWave == nullptr) {
PrintError(TEXT("Passed SoundWave pointer is a nullptr!"));
return;
}
if (InSoundWave->NumChannels < 1 || InSoundWave->NumChannels > 2) {
PrintError(TEXT("SoundWave Object has not the right amount of Channels. Plugin only supports 1 or 2!"));
return;
}
if (GEngine)
{
// Get a Pointer to the Main Audio Device
FAudioDevice* AudioDevice = GEngine->GetMainAudioDevice();
if (AudioDevice) {
InSoundWave->InitAudioResource(AudioDevice->GetRuntimeFormat(InSoundWave));
PrintLog(TEXT("Creating new DecompressWorker."));
// Creates a new DecompressWorker and starts it
InitNewDecompressTask(InSoundWave);
}
else {
PrintError(TEXT("Couldn't get a valid Pointer to the Main AudioDevice!"));
return;
}
}
}
void USoundVisComponent::CalculateFrequencySpectrum(USoundWave* InSoundWaveRef, const float InStartTime, const float InDuration, TArray<float>& OutFrequencies)
{
// Clear the Array before continuing
OutFrequencies.Empty();
const int32 NumChannels = InSoundWaveRef->NumChannels;
const int32 SampleRate = InSoundWaveRef->SampleRate;
// Make sure the Number of Channels is correct
if (NumChannels > 0 && NumChannels <= 2)
{
// Check if we actually have a Buffer to work with
if (InSoundWaveRef->CachedRealtimeFirstBuffer)
{
// The first sample is just the StartTime * SampleRate
int32 FirstSample = SampleRate * InStartTime;
// The last sample is the SampleRate times (StartTime plus the Duration)
int32 LastSample = SampleRate * (InStartTime + InDuration);
// Get Maximum amount of samples in this Sound
const int32 SampleCount = InSoundWaveRef->RawPCMDataSize / (2 * NumChannels);
// An early check if we can create a Sample window
FirstSample = FMath::Min(SampleCount, FirstSample);
LastSample = FMath::Min(SampleCount, LastSample);
// Actual amount of samples we gonna read
int32 SamplesToRead = LastSample - FirstSample;
if (SamplesToRead < 0) {
PrintError(TEXT("Number of SamplesToRead is < 0!"));
return;
}
// Shift the window enough so that we get a PowerOfTwo. FFT works better with that
int32 PoT = 2;
while (SamplesToRead > PoT) {
PoT *= 2;
}
// Now we have a good PowerOfTwo to work with
SamplesToRead = PoT;
// Create two 2-dim Arrays for complex numbers | Buffer and Output
kiss_fft_cpx* Buffer[2] = { 0 };
kiss_fft_cpx* Output[2] = { 0 };
// Create 1-dim Array with one slot for SamplesToRead
int32 Dims[1] = { SamplesToRead };
kiss_fftnd_cfg STF = kiss_fftnd_alloc(Dims, 1, 0, nullptr, nullptr);
int16* SamplePtr = reinterpret_cast<int16*>(InSoundWaveRef->CachedRealtimeFirstBuffer);
// Allocate space in the Buffer and Output Arrays for all the data that FFT returns
for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ChannelIndex++)
{
Buffer[ChannelIndex] = (kiss_fft_cpx*)KISS_FFT_MALLOC(sizeof(kiss_fft_cpx) * SamplesToRead);
Output[ChannelIndex] = (kiss_fft_cpx*)KISS_FFT_MALLOC(sizeof(kiss_fft_cpx) * SamplesToRead);
}
// Shift our SamplePointer to the Current "FirstSample"
SamplePtr += FirstSample * NumChannels;
for (int32 SampleIndex = 0; SampleIndex < SamplesToRead; SampleIndex++)
{
for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ChannelIndex++)
{
// Make sure the Point is Valid and we don't go out of bounds
if (SamplePtr != NULL && (SampleIndex + FirstSample < SampleCount))
{
// Use Window function to get a better result for the Data (Hann Window)
Buffer[ChannelIndex][SampleIndex].r = GetFFTInValue(*SamplePtr, SampleIndex, SamplesToRead);
Buffer[ChannelIndex][SampleIndex].i = 0.f;
}
else
{
// Use Window function to get a better result for the Data (Hann Window)
Buffer[ChannelIndex][SampleIndex].r = 0.f;
Buffer[ChannelIndex][SampleIndex].i = 0.f;
}
// Take the next Sample
SamplePtr++;
}
}
// Now that the Buffer is filled, use the FFT
for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ChannelIndex++)
{
if (Buffer[ChannelIndex])
{
kiss_fftnd(STF, Buffer[ChannelIndex], Output[ChannelIndex]);
}
}
OutFrequencies.AddZeroed(SamplesToRead);
for (int32 SampleIndex = 0; SampleIndex < SamplesToRead; ++SampleIndex)
{
float ChannelSum = 0.0f;
for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ++ChannelIndex)
{
if (Output[ChannelIndex])
{
// With this we get the actual Frequency value for the frequencies from 0hz to ~22000hz
ChannelSum += FMath::Sqrt(FMath::Square(Output[ChannelIndex][SampleIndex].r) + FMath::Square(Output[ChannelIndex][SampleIndex].i));
}
}
if (bNormalizeOutputToDb)
{
OutFrequencies[SampleIndex] = FMath::LogX(10, ChannelSum / NumChannels) * 10;
}
else
{
OutFrequencies[SampleIndex] = ChannelSum / NumChannels;
}
}
// Make sure to free up the FFT stuff
KISS_FFT_FREE(STF);
for (int32 ChannelIndex = 0; ChannelIndex < NumChannels; ++ChannelIndex)
{
KISS_FFT_FREE(Buffer[ChannelIndex]);
KISS_FFT_FREE(Output[ChannelIndex]);
}
}
else {
PrintError(TEXT("InSoundVisData.PCMData is a nullptr!"));
}
}
else {
PrintError(TEXT("Number of Channels is < 0!"));
}
}
float USoundVisComponent::GetFFTInValue(const int16 InSampleValue, const int16 InSampleIndex, const int16 InSampleCount)
{
float FFTValue = InSampleValue;
// Apply the Hann window
FFTValue *= 0.5f * (1 - FMath::Cos(2 * PI * InSampleIndex / (InSampleCount - 1)));
return FFTValue;
}
void USoundVisComponent::InitNewDecompressTask(USoundWave* InSoundWaveRef)
{
// Do we already have a valid Runnable? If not, create a new one
if (FAudioDecompressWorker::Runnable == NULL)
{
// Start Timer that watches the DecompressWorker State
GetWorld()->GetTimerManager().ClearTimer(AudioDecompressTimer);
GetWorld()->GetTimerManager().SetTimer(AudioDecompressTimer, this, &USoundVisComponent::Notify_SoundDecompressed, 0.1f, true);
// Init new Worker and pass the SoundWaveRef to decompress it
FAudioDecompressWorker::Runnable->InitializeWorker(InSoundWaveRef);
}
else if(FAudioDecompressWorker::Runnable->IsFinished())
{
// The Worker is finished and still valid, shut it down!
FAudioDecompressWorker::ShutdownWorker();
// Start Timer that watches the DecompressWorker State
GetWorld()->GetTimerManager().ClearTimer(AudioDecompressTimer);
GetWorld()->GetTimerManager().SetTimer(AudioDecompressTimer, this, &USoundVisComponent::Notify_SoundDecompressed, 0.1f, true);
// Init new Worker and pass the SoundWaveRef to decompress it
FAudioDecompressWorker::Runnable->InitializeWorker(InSoundWaveRef);
}
else {
PrintLog(TEXT("Worker not finished!"));
}
}
void USoundVisComponent::Notify_SoundDecompressed()
{
// If the Worker finished..
if (FAudioDecompressWorker::Runnable->IsFinished())
{
// ..clear the timer and..
GetWorld()->GetTimerManager().ClearTimer(AudioDecompressTimer);
//..broadcast the result to the Blueprint
OnFileLoadCompleted.Broadcast(FAudioDecompressWorker::Runnable->GetSoundWaveRef());
PrintLog(TEXT("Worker finished!"));
}
else {
PrintLog(TEXT("Worker is working!"));
}
}
void USoundVisComponent::HandleFrequencySpectrumCalculation()
{
// Reference to the Client Viewport
UGameViewportClient* Viewport = GetWorld()->GetGameViewport();
// If the Window is not in the foreground, make sure to pause everything, so it won't get async!
if (!Viewport->Viewport->IsForegroundWindow() && bPauseWhenWindowInBackground)
{
PrintLog(TEXT("Window is not in foreground. Pausing!"));
BP_PauseCalculatingFrequencySpectrum();
bSoundPausedByBackgroundWindow = true;
}
// Only proceed if we are not over the duration, or stopped/pause calculating
if (CurrentSoundData && GetWorld()->GetTimerManager().GetTimerElapsed(SoundPlayerTimer) <= CurrentSoundData->Duration && bSoundPlaying)
{
// Dummy Array to Store the Frequencies in
TArray<float> OutFrequencies;
// Let our Function Calculate the Frequency Spectrum
CalculateFrequencySpectrum(CurrentSoundData, GetWorld()->GetTimerManager().GetTimerElapsed(SoundPlayerTimer), CurrentSegmentLength, OutFrequencies);
// Now that this is done, call the Delegate, so the Blueprint can work with it
OnFrequencySpectrumCalculated.Broadcast(OutFrequencies);
// Start this function again after a short delay
FrequencySpectrumTimerDelegate.BindUFunction(this, FName("HandleFrequencySpectrumCalculation"));
GetWorld()->GetTimerManager().SetTimer(FrequencySpectrumTimer, FrequencySpectrumTimerDelegate, 0.01f, false);
}
else
{
GetWorld()->GetTimerManager().ClearTimer(FrequencySpectrumTimer);
}
}
/// Blueprint Versions of the File Data Functions
void USoundVisComponent::BP_LoadSoundFileFromHD(const FString InFilePath)
{
LoadSoundFileFromHD(InFilePath);
}
void USoundVisComponent::BP_LoadAllSoundFileNamesFromHD(bool& bLoaded, const FString InDirectoryPath, const bool bInAbsolutePath, const FString InFileExtension, TArray<FString>& OutSoundFileNamesWithPath, TArray<FString>& OutSoundFileNamesWithoutPath)
{
FString FinalPath = InDirectoryPath;
if (!bInAbsolutePath)
{
FinalPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()) + InDirectoryPath;
}
TArray<FString> DirectoriesToSkip;
IPlatformFile &PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
FLocalTimestampDirectoryVisitor Visitor(PlatformFile, DirectoriesToSkip, DirectoriesToSkip, false);
PlatformFile.IterateDirectory(*FinalPath, Visitor);
for (TMap<FString, FDateTime>::TIterator TimestampIt(Visitor.FileTimes); TimestampIt; ++TimestampIt)
{
const FString FilePath = TimestampIt.Key();
FString FileName = FPaths::GetCleanFilename(FilePath);
bool bShouldAddFile = true;
if (!InFileExtension.IsEmpty())
{
if (!(FPaths::GetExtension(FileName, false).Equals(InFileExtension, ESearchCase::IgnoreCase)))
{
bShouldAddFile = false;
}
}
if (bShouldAddFile)
{
OutSoundFileNamesWithPath.Add(FilePath);
FileName.FString::Split(FString("."), &FileName, nullptr, ESearchCase::IgnoreCase);
OutSoundFileNamesWithoutPath.Add(FileName);
}
}
bLoaded = true;
}
/// Blueprint Versions of the Analyze Functions
void USoundVisComponent::BP_CalculateFrequencySpectrum(USoundWave* InSoundWaveRef, const float InStartTime, const float InDuration, TArray<float>& OutFrequencies)
{
CalculateFrequencySpectrum(InSoundWaveRef, InStartTime, InDuration, OutFrequencies);
}
void USoundVisComponent::BP_StartCalculatingFrequencySpectrum(USoundWave* InSoundWaveRef, const float InSegmentLength)
{
// When the Sound Ref is NULL, better not start analyzing
if (InSoundWaveRef == nullptr) {
PrintError(TEXT("SoundWaveRef is a nullptr. Please load a Sound first!"));
return;
}
// Make sure we are not already playing something
if (!bSoundPlaying)
{
// If we pause, make sure to stop the Player first
if (GetWorld()->GetTimerManager().IsTimerPaused(SoundPlayerTimer))
{
BP_StopCalculatingFrequencySpectrum();
}
// Save the Current SoundVisData
CurrentSoundData = InSoundWaveRef;
// And the Current SegmentLength
CurrentSegmentLength = InSegmentLength;
// Set the Sound and Play it
AudioComponent->SetSound(InSoundWaveRef);
AudioComponent->Play();
// Mark Sound as playing
bSoundPlaying = true;
// Start Timer that is way too long, so we can use it as some kind of AudioPlayer Timer
GetWorld()->GetTimerManager().SetTimer(SoundPlayerTimer, 99999.f, false);
// Start the Calculation Loop
HandleFrequencySpectrumCalculation();
}
else {
PrintWarning(TEXT("AudioComponent is already Playing. Please stop it first!"));
}
}
void USoundVisComponent::BP_PauseCalculatingFrequencySpectrum()
{
// We can only pause, if we are playing
if (bSoundPlaying && !bSoundPaused)
{
// Stop the Audio Component
AudioComponent->Stop();
// Mark sound as being paused
bSoundPaused = true;
// Pause the timer
GetWorld()->GetTimerManager().PauseTimer(SoundPlayerTimer);
// Start the tick, so we can check when the Player is back in the game
SetComponentTickEnabled(true);
}
else {
PrintWarning(TEXT("You can't pause something, that is not playing!"));
}
}
void USoundVisComponent::BP_StopCalculatingFrequencySpectrum()
{
// If we are playing the Sound, or it's paused, stop it
if (bSoundPlaying || bSoundPaused)
{
// Stop the AudioComponent
AudioComponent->Stop();
// Mark Sound as not playing or paused
bSoundPaused = false;
bSoundPlaying = false;
bSoundPausedByBackgroundWindow = false;
// Clear the timer
GetWorld()->GetTimerManager().ClearTimer(SoundPlayerTimer);
// Clear Current SoundVisData
CurrentSoundData = nullptr;
// Clear CurrentSegmentLength
CurrentSegmentLength = 0.0f;
// Stop the Tick Function
SetComponentTickEnabled(false);
}
else {
PrintWarning(TEXT("You can't stop something, that is not playing or paused!"));
}
}
void USoundVisComponent::BP_ResumeCalculatingFrequencySpectrum()
{
if (bSoundPlaying && bSoundPaused)
{
// Start the Sound at the Point where we left it
AudioComponent->Play(GetWorld()->GetTimerManager().GetTimerElapsed(SoundPlayerTimer));
// Mark sound as no longer paused
bSoundPaused = false;
// UnPause the Timer again
GetWorld()->GetTimerManager().UnPauseTimer(SoundPlayerTimer);
// And start the Calculation again
HandleFrequencySpectrumCalculation();
// Stop the Tick Function
SetComponentTickEnabled(false);
}
else {
PrintWarning(TEXT("AudioComponent is Playing or not paused!"));
}
}
bool USoundVisComponent::IsPlayerPlaying()
{
return (bSoundPlaying && !bSoundPaused);
}
bool USoundVisComponent::IsPlayerPaused()
{
UWorld* World = GetWorld();
if (World)
{
return (bSoundPlaying && bSoundPaused);
}
return false;
}
float USoundVisComponent::GetCurrentPlayBackTime()
{
UWorld* World = GetWorld();
if (World)
{
return World->GetTimerManager().GetTimerElapsed(SoundPlayerTimer);
}
return 0.0f;
}
/// Frequency Data Functions
void USoundVisComponent::BP_GetSpecificFrequencyValue(USoundWave* InSoundWave, TArray<float> InFrequencies, int32 InWantedFrequency, float& OutFrequencyValue)
{
if (InSoundWave && InFrequencies.Num() > 0 && (int32)(InWantedFrequency * InFrequencies.Num() * 2 / InSoundWave->SampleRate) < InFrequencies.Num())
{
OutFrequencyValue = InFrequencies[(int32)(InWantedFrequency * InFrequencies.Num() * 2 / InSoundWave->SampleRate)];
}
}
void USoundVisComponent::BP_GetAverageSubBassValue(USoundWave* InSoundWave, TArray<float> InFrequencies, float& OutAverageSubBass)
{
BP_GetAverageFrequencyValueInRange(InSoundWave, InFrequencies, 20, 60, OutAverageSubBass);
}
void USoundVisComponent::BP_GetAverageBassValue(USoundWave* InSoundWave, TArray<float> InFrequencies, float& OutAverageBass)
{
if (InSoundWave)
{
BP_GetAverageFrequencyValueInRange(InSoundWave, InFrequencies, 60, 250, OutAverageBass);
}
}
void USoundVisComponent::BP_GetAverageFrequencyValueInRange(USoundWave* InSoundWave, TArray<float> InFrequencies, int32 InStartFrequence, int32 InEndFrequence, float& OutAverageFrequency)
{
// Init the Return Value
OutAverageFrequency = 0.0f;
if (InSoundWave == nullptr)
return;
if (InStartFrequence >= InEndFrequence || InStartFrequence < 0 || InEndFrequence > 22000)
return;
int32 FStart = (int32)(InStartFrequence * InFrequencies.Num() * 2 / InSoundWave->SampleRate);
int32 FEnd = (int32)(InEndFrequence * InFrequencies.Num() * 2 / InSoundWave->SampleRate);
if (FStart < 0 || FEnd >= InFrequencies.Num())
return;
int32 NumberOfFrequencies = 0;
float ValueSum = 0.0f;
for (int i = FStart; i <= FEnd; i++)
{
NumberOfFrequencies++;
ValueSum += InFrequencies[i];
}
OutAverageFrequency = ValueSum / NumberOfFrequencies;
}

View File

@@ -0,0 +1,17 @@
#include "eXiSoundVisPrivatePCH.h"
// Log Category
DEFINE_LOG_CATEGORY(LogeXiSoundVis);
void FeXiSoundVisModule::StartupModule()
{
// This code will execute after your module is loaded into memory (but after global variables are initialized, of course.)
}
void FeXiSoundVisModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
IMPLEMENT_MODULE(FeXiSoundVisModule, eXiSoundVis)

View File

@@ -0,0 +1,16 @@
#pragma once
#include "Engine.h"
#include "eXiSoundVis.h"
// KISS Headers, that we need for the decompression part
#include "ThirdParty/Kiss_FFT/kiss_fft129/kiss_fft.h"
#include "ThirdParty/Kiss_FFT/kiss_fft129/tools/kiss_fftnd.h"
// Log Category
DECLARE_LOG_CATEGORY_EXTERN(LogeXiSoundVis, Log, All);
// Short Defines to faster debug
#define PrintLog(TextToLog) if(bShowLogDebug) UE_LOG(LogeXiSoundVis, Log, TextToLog)
#define PrintWarning(TextToLog) if(bShowWarningDebug) UE_LOG(LogeXiSoundVis, Warning, TextToLog)
#define PrintError(TextToLog) if(bShowErrorDebug) UE_LOG(LogeXiSoundVis, Error, TextToLog)

View File

@@ -0,0 +1,73 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "AudioDecompress.h"
//#include "AudioDecompressWorker.generated.h"
class FAudioDecompressWorker : public FRunnable
{
/// VARIABLES ///
public:
// Singleton Instance, can access the Thread any time via static accessors
static FAudioDecompressWorker* Runnable;
// Thread to run the worker FRunnable on
FRunnableThread* Thread;
private:
// Stop this thread? Uses Thread Safe Counter
FThreadSafeCounter StopTaskCounter;
// Bool to check if the thread is running
bool bIsFinished;
// Counter for the ThreadNames
static int32 ThreadCounter;
protected:
// Pointer to SoundWave Object we want to Decompress
class USoundWave* SoundWaveRef;
// Some Compressed Audio Information
ICompressedAudioInfo* AudioInfo;
/// FUNCTIONS ///
public:
// Constructor for my De-Compressor
FAudioDecompressWorker(class USoundWave* InSoundWaveRef);
~FAudioDecompressWorker();
// Custom Init function
static FAudioDecompressWorker* InitializeWorker(class USoundWave* InSoundWaveRef);
// Custom Shutdown function
static void ShutdownWorker();
// Start FRunnable Interface
virtual bool Init();
virtual uint32 Run();
virtual void Stop();
virtual void Exit();
// End FRunnable Interface
// Make sure Thread completed
void EnsureCompletion();
/// GETTER / SETTER
class USoundWave* GetSoundWaveRef() const {
return SoundWaveRef;
}
bool IsFinished() const {
return bIsFinished;
}
};

View File

@@ -0,0 +1,305 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "Components/ActorComponent.h"
#include "AudioDecompress.h"
// Workers, to have an Async Decompress worker
#include "Runtime/Core/Public/Async/AsyncWork.h"
#include "AudioDecompressWorker.h"
#include "SoundVisComponent.generated.h"
USTRUCT(BlueprintType, Blueprintable)
struct FSoundVisData
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadOnly, Category = "eXiSoundVis | Sounds")
class USoundWave* SoundWaveRef;
TSharedPtr<uint8> PCMData;
~FSoundVisData()
{
SoundWaveRef = nullptr;
PCMData.Reset();
}
};
// Delegate that passes FSoundVisData
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFileLoadCompleted, USoundWave*, SoundWaveRef);
// Delegate used by the Worker
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FWorkerFinished, USoundWave*, SoundWaveRef);
// Delegate that passes FrequencyValues
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFrequencySpectrumCalculated, const TArray<float>&, OutFrequencies);
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent), meta=(DisplayName = "SoundVisComponent") )
class EXISOUNDVIS_API USoundVisComponent : public UActorComponent
{
GENERATED_BODY()
/// VARIABLES ///
private:
// Reference to the SoundWave we are decompressing
USoundWave* CompressedSoundWaveRef;
/// Blueprint Exposed Variables
public:
// Audio Component to Play our Audio with
UPROPERTY(BlueprintReadOnly, Category = "SoundVis | Sound Player")
class UAudioComponent* AudioComponent;
// TimerHandle for the SoundPlayer, that only exists to check how long a Sound is running
UPROPERTY(BlueprintReadOnly, Category = "SoundVis | Frequency Spectrum")
FTimerHandle SoundPlayerTimer;
UPROPERTY(BlueprintReadOnly, Category = "SoundVis | Sound Player")
bool bSoundPlaying;
UPROPERTY(BlueprintReadOnly, Category = "SoundVis | Sound Player")
bool bSoundPaused;
UPROPERTY(BlueprintReadOnly, Category = "SoundVis | Sound Player")
bool bSoundPausedByBackgroundWindow = false;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "SoundVis | Sound Player")
bool bPauseWhenWindowInBackground = true;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "SoundVis | Sound Player")
bool bNormalizeOutputToDb = false;
/// Delegates
// Blueprint Delegate that gets Broadcasted when the File is loaded completely
UPROPERTY(BlueprintAssignable)
FFileLoadCompleted OnFileLoadCompleted;
// Blueprint Delegate that gets Broadcasted each time the Frequency Spectrum is calculated
UPROPERTY(BlueprintAssignable)
FFrequencySpectrumCalculated OnFrequencySpectrumCalculated;
/// Debug
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "SoundVis | Debugging")
bool bShowLogDebug;
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "SoundVis | Debugging")
bool bShowWarningDebug;
UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = "SoundVis | Debugging")
bool bShowErrorDebug;
private:
// The CurrentSoundData is only valid if the AudioPlayer is paused or running!
USoundWave* CurrentSoundData;
// The CurrentSegmentLength is only valid if the AudioPlayer is pause or running
float CurrentSegmentLength;
// Timer and Delegate for the DecompressWorker
FTimerHandle AudioDecompressTimer;
// Timer and Delegate for the auto calculation of the spectrum
FTimerHandle FrequencySpectrumTimer;
FTimerDelegate FrequencySpectrumTimerDelegate;
/// FUNCTIONS ///
public:
/// De-/Constructors
// Sets default values for this component's properties
USoundVisComponent();
// Cleans up stuff
~USoundVisComponent();
/// Overrides
// Tick called each frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
/// Functions to load Data from the HardDrive
// Function to load a sound file from the HD
void LoadSoundFileFromHD(const FString& InFilePath);
// Function to fill in the RawFile sound data into the USoundWave object
bool FillSoundWaveInfo(class USoundWave* InSoundWave, TArray<uint8>* InRawFile);
/// Function to decompress the compressed Data that comes with the .ogg file
void GetPCMDataFromFile(class USoundWave* InSoundWave);
/// Function to calculate the frequency spectrum
void CalculateFrequencySpectrum(USoundWave* InSoundWaveRef, const float InStartTime, const float InDuration, TArray<float>& OutFrequencies);
/// Helper Functions
// Function used to get a better value for the FFT. Uses Hann Window
float GetFFTInValue(const int16 InSampleValue, const int16 InSampleIndex, const int16 InSampleCount);
// Function to Start a new DecompressTask
void InitNewDecompressTask(USoundWave* InSoundWaveRef);
// DEBUG Test function to check if Task can call stuff in here
void Notify_SoundDecompressed();
// Function that is looped to handle the calculation of the FrequencySpectrum
UFUNCTION()
void HandleFrequencySpectrumCalculation();
/// Blueprint Versions of the File Data Functions
/**
* Will load a file (currently .ogg) from your Hard-Drive and save it in a USoundWave variable
*
* @param InFilePath Absolute path to the File. E.g.: "C:/Sounds/File.ogg"
*
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Load Sound File"), Category = "SoundVis | SoundFile")
void BP_LoadSoundFileFromHD(const FString InFilePath);
/**
* Will get an Array of Names of the Found SoundFiles
*
* @param InDirectoryPath Path to the Directory in which the Files are (absolute/relative)
* @param bInAbsolutePath Tells if the DirectoryPath is absolute (C:/..) or relative to the GameDirectory
* @param InFileExtension This is the Extension the Function should look for. For the Plugin it should be .ogg
* @param OutSoundFileNamesWithPath The Array of found SoundFileNames (full Path/Name.Extension)
* @param OutSoundFileNamesWithoutPath The Array of found SoundFileNames (only Name.Extension)
*
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Load Sound File Names"), Category = "SoundVis | SoundFile")
void BP_LoadAllSoundFileNamesFromHD(bool& bLoaded, const FString InDirectoryPath, const bool bInAbsolutePath, const FString InFileExtension, TArray<FString>& OutSoundFileNamesWithPath, TArray<FString>& OutSoundFileNamesWithoutPath);
/**
* Will call the CalculateFrequencySpectrum function from BP Side
*
* @param InSoundWave SoundWave that gets analyzed
* @param InStartTime The StartPoint of the TimeWindow we want to analyze
* @param InDuration The length of the TimeWindow we want to analyze
* @param OutFrequencies Array of float values for x Frequencies from 0 to 22000
*
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Calculate Freq Spectrum"), Category = "SoundVis | Frequency Spectrum")
void BP_CalculateFrequencySpectrum(USoundWave* InSoundWaveRef, const float InStartTime, const float InDuration, TArray<float>& OutFrequencies);
/**
* Will play the passed USoundWave and also start calculating the FrequencySpectrum (loops over the InSegmentLength sized parts)
*
* @param InSoundWaveRef SoundWave that gets started and analyzed
* @param InSegmentLength Length of the SoundWave segment that should get analyzed
*
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Start Calculate Freq Spectrum"), Category = "SoundVis | Frequency Spectrum")
void BP_StartCalculatingFrequencySpectrum(USoundWave* InSoundWaveRef, const float InSegmentLength);
/**
* If playing, pauses the current playing USoundWave and FrequencySpectrum calculation
*
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Pause Calculate Freq Spectrum"), Category = "SoundVis | Frequency Spectrum")
void BP_PauseCalculatingFrequencySpectrum();
/**
* If playing or paused, stops the current USoundWave and FrequencySpectrum calculation
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Stop Calculate Freq Spectrum"), Category = "SoundVis | Frequency Spectrum")
void BP_StopCalculatingFrequencySpectrum();
/**
* If paused, resumes the current USoundWave and FrequencySpectrum calculation
*
*/
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Resume Calculate Freq Spectrum"), Category = "SoundVis | Frequency Spectrum")
void BP_ResumeCalculatingFrequencySpectrum();
/// Sound Player Information
/**
* Returns if the Player is currently playing or not
*
* @return True if playing
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Player Playing"), Category = "SoundVis | SoundPlayer")
bool IsPlayerPlaying();
/**
* Returns if the Player is currently paused or not
*
* @return True if paused
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Player Paused"), Category = "SoundVis | SoundPlayer")
bool IsPlayerPaused();
/**
* Return the current PlayBack Time of the Sound Player Timer
*
* @return Current PlayBack Time
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Current Playback Time"), Category = "SoundVis | SoundPlayer")
float GetCurrentPlayBackTime();
/// Frequency Data Functions
/**
* This function will return the value of a specific frequency. It's needs a Frequency Array from the "BP_CalculateFrequencySpectrum" function and the matching SoundWave
*
* @param InSoundWave SoundWave to get specific data from (SampleRate)
* @param InFrequencies Array of float values for different frequencies from 0 to 22000. Can be get by using the "BP_CalculateFrequencySpectrum" function
* @param InWantedFrequency The Frequency of which you want the value of
* @param OutFrequencyValue Float value of the requested frequency
*
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Freq Value"), Category = "SoundVis | Frequency Values")
static void BP_GetSpecificFrequencyValue(USoundWave* InSoundWave, TArray<float> InFrequencies, int32 InWantedFrequency, float& OutFrequencyValue);
/**
* This function will return the average value for SubBass (20 to 60hz)
*
* @param InSoundWave SoundWave to get specific data from (SampleRate)
* @param InFrequencies Array of float values for different frequencies from 0 to 22000. Can be get by using the "BP_CalculateFrequencySpectrum" function
* @param OutAverageSubBass Average value of the frequencies from 20 to 60
*
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Average Subbass Value"), Category = "SoundVis | Frequency Values")
static void BP_GetAverageSubBassValue(USoundWave* InSoundWave, TArray<float> InFrequencies, float& OutAverageSubBass);
/**
* This function will return the average value for Bass (60 to 250hz)
*
* @param InSoundWave SoundWave to get specific data from (SampleRate)
* @param InFrequencies Array of float values for different frequencies from 0 to 22000. Can be get by using the "BP_CalculateFrequencySpectrum" function
* @param OutAverageBass Average value of the frequencies from 60 to 250
*
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Average Bass Value"), Category = "SoundVis | Frequency Values")
static void BP_GetAverageBassValue(USoundWave* InSoundWave, TArray<float> InFrequencies, float& OutAverageBass);
/**
* This function will return the average value for a given frequency interval e.g.: 20 to 60 (SubBass)
*
* @param InSoundWave SoundWave to get specific data from (SampleRate)
* @param InFrequencies Array of float values for different frequencies from 0 to 22000. Can be get by using the "BP_CalculateFrequencySpectrum" function
* @param InStartFrequency Start Frequency of the Frequency interval
* @param InEndFrequency End Frequency of the Frequency interval
* @param OutAverageFrequency Average value of the requested frequency interval
*
*/
UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Average Freq Value In Range"), Category = "SoundVis | Frequency Values")
static void BP_GetAverageFrequencyValueInRange(USoundWave* InSoundWave, TArray<float> InFrequencies, int32 InStartFrequence, int32 InEndFrequence, float& OutAverageFrequency);
};

View File

@@ -0,0 +1,12 @@
#pragma once
#include "ModuleManager.h"
class FeXiSoundVisModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@@ -0,0 +1,17 @@
using UnrealBuildTool;
using System.IO;
public class eXiSoundVis : ModuleRules
{
public eXiSoundVis(ReadOnlyTargetRules Target) : base(Target)
{
PrivateIncludePaths.AddRange(new string[] { "eXiSoundVis/Private" });
PublicIncludePaths.AddRange(new string[] { "eXiSoundVis/Public" });
PublicDependencyModuleNames.AddRange(new string[] { "Engine", "Core", "CoreUObject", "InputCore", "RHI", "Kiss_FFT" });
//PublicAdditionalLibraries.Add("legacy_stdio_definitions.lib");
PublicDependencyModuleNames.Add("Kiss_FFT");
}
}

View File

@@ -0,0 +1,18 @@
{
"FileVersion": 3,
"FriendlyName": "eXi's Sound Visualization Plugin",
"Version": 1,
"VersionName": "1.0",
"Description": "This plugin allows you to load .ogg files dynamically from the user's hard drive and then anaylize it. Analyzing it, will return a frequency spectrum for the given Samples. The spectrum can then be used in your project to control all kinds of things.",
"Category": "eXi.Sound",
"CreatedBy": "Cedric 'eXi' Neukirchen",
"Modules":
[
{
"Name" : "eXiSoundVis",
"Type" : "Runtime",
"LoadingPhase" : "PreDefault"
}
]
}