// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" namespace Audio { // Utility for managing a grain data (enveloped buffer) namespace Grain { typedef TArray FEnvelope; // Grain envelope types enum class EEnvelope { Gaussian, Triangle, DownwardTriangle, UpwardTriangle, ExponentialDecay, ExponentialAttack, Hann }; // Utility function generates an envelope with the input array for the number of desired frames static void GenerateEnvelopeData(FEnvelope& InData, const int32 InNumFrames, const EEnvelope InEnvelopeType) { InData.Reset(); InData.AddUninitialized(InNumFrames); const float N = static_cast(InNumFrames); const float N_1 = N - 1.0f; float n = 0.0f; switch (InEnvelopeType) { case EEnvelope::Gaussian: { const float Denominator = 0.3f * N_1 / 2.0f; for (int32 i = 0; i < InNumFrames; ++i, n += 1.0f) { InData[i] = FMath::Exp(-0.5f * FMath::Pow((n - 0.5f * N_1) / Denominator, 2.0f)); } } break; case EEnvelope::Triangle: { const float A = 0.5f * N_1; for (int32 i = 0; i < InNumFrames; ++i, n += 1.0f) { InData[i] = 1.0f - FMath::Abs((n - A) / A); } } break; case EEnvelope::DownwardTriangle: { for (int32 i = 0; i < InNumFrames; ++i, n += 1.0f) { InData[i] = 1.0f - n / N_1; } } break; case EEnvelope::UpwardTriangle: { for (int32 i = 0; i < InNumFrames; ++i, n += 1.0f) { InData[i] = n / N_1; } } break; case EEnvelope::ExponentialDecay: { for (int32 i = 0; i < InNumFrames; ++i, n += 1.0f) { InData[i] = FMath::Pow((n - N + 1.0f) / N_1, 4.0f); } } break; case EEnvelope::ExponentialAttack: { for (int32 i = 0; i < InNumFrames; ++i, n += 1.0f) { InData[i] = FMath::Pow(n / N_1, 4.0f); } } break; case EEnvelope::Hann: { for (int32 i = 0; i < InNumFrames; ++i, n += 1.0f) { InData[i] = 0.5f - 0.5f * FMath::Cos(2.0f * PI * n / N_1); } } break; } } // Returns the interpolated envelope value given the fraction through the grain static float GetValue(const FEnvelope& InGrainData, const float InFraction) { const int32 NumFrames = InGrainData.Num(); const float Frame = static_cast(InFraction) * (NumFrames - 1); const int32 PrevIndex = static_cast(Frame); const int32 NextIndex = FMath::Min(NumFrames, PrevIndex + 1); const float AlphaIndex = Frame - static_cast(PrevIndex); const float* GrainEnvelopePtr = InGrainData.GetData(); return FMath::Lerp(GrainEnvelopePtr[PrevIndex], GrainEnvelopePtr[NextIndex], AlphaIndex); } } }