init
This commit is contained in:
106
DataPRO/FftSharp/Complex.cs
Normal file
106
DataPRO/FftSharp/Complex.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FftSharp
|
||||
{
|
||||
public struct Complex
|
||||
{
|
||||
private double _real;
|
||||
public double Real { get => _real; set { _real = value; _magnitude = null; _magnitudeSquared = null; } }
|
||||
|
||||
private double _imaginary;
|
||||
public double Imaginary { get => _imaginary; set { _imaginary = value; _magnitude = null; _magnitudeSquared = null; } }
|
||||
|
||||
private double? _magnitudeSquared;
|
||||
public double MagnitudeSquared
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == _magnitudeSquared)
|
||||
_magnitudeSquared = CalculateMagnitudeSquared(Real, Imaginary);
|
||||
return (double)_magnitudeSquared;
|
||||
}
|
||||
}
|
||||
|
||||
private double? _magnitude;
|
||||
public double Magnitude
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == _magnitude)
|
||||
_magnitude = CalculateMagnitude(Real, Imaginary);
|
||||
return (double)_magnitude;
|
||||
}
|
||||
}
|
||||
|
||||
public static Complex Conjugate(Complex a)
|
||||
{
|
||||
return new Complex(a.Real, -a.Imaginary);
|
||||
}
|
||||
|
||||
public Complex(double real, double imaginary)
|
||||
{
|
||||
_real = real;
|
||||
_imaginary = imaginary;
|
||||
_magnitude = CalculateMagnitude(real, imaginary);
|
||||
_magnitudeSquared = CalculateMagnitudeSquared(real, imaginary);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Imaginary < 0)
|
||||
return $"{Real}-{-Imaginary}j";
|
||||
else
|
||||
return $"{Real}+{Imaginary}j";
|
||||
}
|
||||
|
||||
public static Complex operator +(Complex a, Complex b)
|
||||
{
|
||||
return new Complex(a.Real + b.Real, a.Imaginary + b.Imaginary);
|
||||
}
|
||||
|
||||
public static Complex operator -(Complex a, Complex b)
|
||||
{
|
||||
return new Complex(a.Real - b.Real, a.Imaginary - b.Imaginary);
|
||||
}
|
||||
|
||||
public static Complex operator *(Complex a, Complex b)
|
||||
{
|
||||
return new Complex(
|
||||
real: (a.Real * b.Real) - (a.Imaginary * b.Imaginary),
|
||||
imaginary: (a.Real * b.Imaginary) + (a.Imaginary * b.Real));
|
||||
}
|
||||
|
||||
public static Complex operator *(Complex a, double b)
|
||||
{
|
||||
return new Complex(a.Real * b, a.Imaginary * b);
|
||||
}
|
||||
|
||||
public static Complex[] FromReal(double[] real)
|
||||
{
|
||||
Complex[] complex = new Complex[real.Length];
|
||||
for (int i = 0; i < real.Length; i++)
|
||||
complex[i].Real = real[i];
|
||||
return complex;
|
||||
}
|
||||
|
||||
private static double CalculateMagnitudeSquared(double real, double imaginary)
|
||||
{
|
||||
return real * real + imaginary * imaginary;
|
||||
}
|
||||
|
||||
private static double CalculateMagnitude(double real, double imaginary)
|
||||
{
|
||||
return Math.Sqrt(CalculateMagnitudeSquared(real, imaginary));
|
||||
}
|
||||
|
||||
public static double[] GetMagnitudes(Complex[] input)
|
||||
{
|
||||
double[] output = new double[input.Length];
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
output[i] = input[i].Magnitude;
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
DataPRO/FftSharp/Experimental.cs
Normal file
71
DataPRO/FftSharp/Experimental.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FftSharp
|
||||
{
|
||||
[Obsolete("This module is for educational purposes only")]
|
||||
public static class Experimental
|
||||
{
|
||||
// An easy-to-read (but non-optimized) implementation of the FFT algorithm
|
||||
private static Complex[] FFTsimple(Complex[] input)
|
||||
{
|
||||
Complex[] output = new Complex[input.Length];
|
||||
|
||||
int H = input.Length / 2;
|
||||
Complex[] evens = new Complex[H];
|
||||
Complex[] odds = new Complex[H];
|
||||
for (int i = 0; i < H; i++)
|
||||
{
|
||||
evens[i] = input[2 * i];
|
||||
odds[i] = input[2 * i + 1];
|
||||
}
|
||||
odds = FFTsimple(odds);
|
||||
evens = FFTsimple(evens);
|
||||
|
||||
double mult1 = -2 * Math.PI / input.Length;
|
||||
for (int i = 0; i < H; i++)
|
||||
{
|
||||
double radians = mult1 * i;
|
||||
odds[i] *= new Complex(Math.Cos(radians), Math.Sin(radians));
|
||||
}
|
||||
|
||||
for (int i = 0; i < H; i++)
|
||||
{
|
||||
output[i] = evens[i] + odds[i];
|
||||
output[i + H] = evens[i] - odds[i];
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Compute the forward or inverse discrete Fourier Transform (not using the fast FFT algorithm)
|
||||
public static Complex[] DFT(Complex[] input, bool inverse = false)
|
||||
{
|
||||
int N = input.Length;
|
||||
double mult1 = (inverse) ? 2 * Math.PI / N : -2 * Math.PI / N;
|
||||
double mult2 = (inverse) ? 1.0 / N : 1.0;
|
||||
Console.WriteLine($"REAL {mult1} {mult2}");
|
||||
|
||||
Complex[] output = new Complex[N];
|
||||
for (int k = 0; k < N; k++)
|
||||
{
|
||||
output[k] = new Complex(0, 0);
|
||||
for (int n = 0; n < N; n++)
|
||||
{
|
||||
double radians = n * k * mult1;
|
||||
Complex temp = new Complex(Math.Cos(radians), Math.Sin(radians));
|
||||
temp *= input[n];
|
||||
output[k] += temp * mult2;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static Complex[] DFT(double[] input, bool inverse = false)
|
||||
{
|
||||
return DFT(Transform.MakeComplex(input), inverse);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
DataPRO/FftSharp/FftSharp.csproj
Normal file
100
DataPRO/FftSharp/FftSharp.csproj
Normal file
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{9FF2BEB4-A267-4139-A37D-9C9A58D7D36D}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>FftSharp</RootNamespace>
|
||||
<AssemblyName>FftSharp</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Complex.cs" />
|
||||
<Compile Include="Experimental.cs" />
|
||||
<Compile Include="Filter.cs" />
|
||||
<Compile Include="IWindow.cs" />
|
||||
<Compile Include="Pad.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SampleData.cs" />
|
||||
<Compile Include="Transform.cs" />
|
||||
<Compile Include="Window.cs" />
|
||||
<Compile Include="Windows\Bartlett.cs" />
|
||||
<Compile Include="Windows\BlackmanHarris.cs" />
|
||||
<Compile Include="Windows\Blackman.cs" />
|
||||
<Compile Include="Windows\Cosine.cs" />
|
||||
<Compile Include="Windows\FlatTop.cs" />
|
||||
<Compile Include="Windows\Hamming.cs" />
|
||||
<Compile Include="Windows\Hanning.cs" />
|
||||
<Compile Include="Windows\Kaiser.cs" />
|
||||
<Compile Include="Windows\Rectangular.cs" />
|
||||
<Compile Include="Windows\Tukey.cs" />
|
||||
<Compile Include="Windows\Welch.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Common\DTS.Common.Utilities\DTS.Common.Utilities.csproj">
|
||||
<Project>{d6da1b74-c711-43c2-91b1-1908a8d04dbf}</Project>
|
||||
<Name>DTS.Common.Utilities</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Common\DTS.Common\DTS.Common.csproj">
|
||||
<Project>{f7a0804f-61a4-40ae-83d0-f1137622b592}</Project>
|
||||
<Name>DTS.Common</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="FftSharp.xml" />
|
||||
<Content Include="icon.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
219
DataPRO/FftSharp/FftSharp.xml
Normal file
219
DataPRO/FftSharp/FftSharp.xml
Normal file
@@ -0,0 +1,219 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>FftSharp</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="M:FftSharp.Filter.LowPass(System.Double[],System.Double,System.Double)">
|
||||
<summary>
|
||||
Silence frequencies above the given frequency
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Filter.HighPass(System.Double[],System.Double,System.Double)">
|
||||
<summary>
|
||||
Silence frequencies below the given frequency
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Filter.BandPass(System.Double[],System.Double,System.Double,System.Double)">
|
||||
<summary>
|
||||
Silence frequencies outside the given frequency range
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Filter.BandStop(System.Double[],System.Double,System.Double,System.Double)">
|
||||
<summary>
|
||||
Silence frequencies inside the given frequency range
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.IWindow.Create(System.Int32,System.Boolean)">
|
||||
<summary>
|
||||
Generate this window as a new array with the given length.
|
||||
Normalizing will scale the window so the sum of all points is 1.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.IWindow.Apply(System.Double[],System.Boolean)">
|
||||
<summary>
|
||||
Return a new array where this window was multiplied by the given signal.
|
||||
Normalizing will scale the window so the sum of all points is 1 prior to multiplication.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.IWindow.ApplyInPlace(System.Double[],System.Boolean)">
|
||||
<summary>
|
||||
Modify the given signal by multiplying it by this window IN PLACE.
|
||||
Normalizing will scale the window so the sum of all points is 1 prior to multiplication.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FftSharp.IWindow.Name">
|
||||
<summary>
|
||||
Single word name for this window
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:FftSharp.IWindow.Description">
|
||||
<summary>
|
||||
A brief description of what makes this window unique and what it is typically used for.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Pad.IsPowerOfTwo(System.Int32)">
|
||||
<summary>
|
||||
Test if a number is an even power of 2
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Pad.ZeroPad(FftSharp.Complex[])">
|
||||
<summary>
|
||||
Return the input array (or a new zero-padded new one) ensuring length is a power of 2
|
||||
</summary>
|
||||
<param name="input">array of any length</param>
|
||||
<returns>the input array or a zero-padded copy</returns>
|
||||
</member>
|
||||
<member name="M:FftSharp.Pad.ZeroPad(System.Double[])">
|
||||
<summary>
|
||||
Return the input array (or a new zero-padded new one) ensuring length is a power of 2
|
||||
</summary>
|
||||
<param name="input">array of any length</param>
|
||||
<returns>the input array or a zero-padded copy</returns>
|
||||
</member>
|
||||
<member name="M:FftSharp.Pad.ZeroPad(FftSharp.Complex[],System.Int32)">
|
||||
<summary>
|
||||
Return the input array zero-padded to reach a final length
|
||||
</summary>
|
||||
<param name="input">array of any length</param>
|
||||
<param name="finalLength">pad the array with zeros a the end to achieve this final length</param>
|
||||
<returns>a zero-padded copy of the input array</returns>
|
||||
</member>
|
||||
<member name="M:FftSharp.Pad.ZeroPad(System.Double[],System.Int32)">
|
||||
<summary>
|
||||
Return the input array zero-padded to reach a final length
|
||||
</summary>
|
||||
<param name="input">array of any length</param>
|
||||
<param name="finalLength">pad the array with zeros a the end to achieve this final length</param>
|
||||
<returns>a zero-padded copy of the input array</returns>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.FFT(FftSharp.Complex[])">
|
||||
<summary>
|
||||
Compute the discrete Fourier Transform (in-place) using the FFT algorithm.
|
||||
</summary>
|
||||
<param name="buffer">Data to transform in-place. Length must be a power of 2.</param>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.FFT(System.Span{FftSharp.Complex})">
|
||||
<summary>
|
||||
Compute the discrete Fourier Transform (in-place) using the FFT algorithm.
|
||||
</summary>
|
||||
<param name="buffer">Data to transform in-place. Length must be a power of 2.</param>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.FFT_WithoutChecks(System.Span{FftSharp.Complex})">
|
||||
<summary>
|
||||
High performance FFT function.
|
||||
Complex input will be transformed in place.
|
||||
Input array length must be a power of two. This length is NOT validated.
|
||||
Running on an array with an invalid length may throw an invalid index exception.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.IFFT(FftSharp.Complex[])">
|
||||
<summary>
|
||||
Compute the inverse discrete Fourier Transform (in-place) using the FFT algorithm.
|
||||
</summary>
|
||||
<param name="buffer">Data to transform in-place. Length must be a power of 2.</param>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.BitReverse(System.Int32,System.Int32)">
|
||||
<summary>
|
||||
Reverse the sequence of bits in an integer (01101 -> 10110)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.FFTfreq(System.Double,System.Int32,System.Boolean)">
|
||||
<summary>
|
||||
Calculate sample frequency for each point in a FFT
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.FFTfreqPeriod(System.Int32,System.Int32)">
|
||||
<summary>
|
||||
Return the distance between each FFT point in frequency units (Hz)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.IsPowerOfTwo(System.Int32)">
|
||||
<summary>
|
||||
Test if a number is an even power of 2
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.MakeComplex(System.Double[])">
|
||||
<summary>
|
||||
Create an array of Complex data given the real component
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.MakeComplex(System.Span{FftSharp.Complex},System.Span{System.Double})">
|
||||
<summary>
|
||||
Create an array of Complex data given the real component
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.FFT(System.Double[])">
|
||||
<summary>
|
||||
Compute the 1D discrete Fourier Transform using the Fast Fourier Transform (FFT) algorithm
|
||||
</summary>
|
||||
<param name="input">real input (must be an array with length that is a power of 2)</param>
|
||||
<returns>transformed input</returns>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.RFFT(System.Double[])">
|
||||
<summary>
|
||||
Compute the 1D discrete Fourier Transform using the Fast Fourier Transform (FFT) algorithm
|
||||
</summary>
|
||||
<param name="input">real input (must be an array with length that is a power of 2)</param>
|
||||
<returns>real component of transformed input</returns>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.RFFT(System.Span{FftSharp.Complex},System.Span{System.Double})">
|
||||
<summary>
|
||||
Compute the 1D discrete Fourier Transform using the Fast Fourier Transform (FFT) algorithm
|
||||
</summary>
|
||||
<param name="destination">Memory location of the results (must be an equal to input length / 2 + 1)</param>
|
||||
<param name="input">real input (must be an array with length that is a power of 2)</param>
|
||||
<returns>real component of transformed input</returns>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.Absolute(FftSharp.Complex[])">
|
||||
<summary>
|
||||
Return a Complex array as an array of its absolute values
|
||||
</summary>
|
||||
<param name="input"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.FFTmagnitude(System.Double[])">
|
||||
<summary>
|
||||
Calculte power spectrum density (PSD) original (RMS) units
|
||||
</summary>
|
||||
<param name="input">real input</param>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.FFTmagnitude(System.Span{System.Double},System.Span{System.Double})">
|
||||
<summary>
|
||||
Calculte power spectrum density (PSD) original (RMS) units
|
||||
</summary>
|
||||
<param name="destination">Memory location of the results.</param>
|
||||
<param name="input">real input</param>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.FFTpower(System.Double[])">
|
||||
<summary>
|
||||
Calculte power spectrum density (PSD) in dB units
|
||||
</summary>
|
||||
<param name="input">real input</param>
|
||||
</member>
|
||||
<member name="M:FftSharp.Transform.FFTpower(System.Span{System.Double},System.Double[])">
|
||||
<summary>
|
||||
Calculte power spectrum density (PSD) in dB units
|
||||
</summary>
|
||||
<param name="destination">Memory location of the results.</param>
|
||||
<param name="input">real input</param>
|
||||
</member>
|
||||
<member name="M:FftSharp.Window.Apply(System.Double[],System.Boolean)">
|
||||
<summary>
|
||||
Multiply the array by this window and return the result as a new array
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Window.ApplyInPlace(System.Double[],System.Boolean)">
|
||||
<summary>
|
||||
Multiply the array by this window, modifying it in place
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FftSharp.Window.GetWindows">
|
||||
<summary>
|
||||
Return an array containing all available windows.
|
||||
Note that all windows returned will use the default constructor, but some
|
||||
windows have customization options in their constructors if you create them individually.
|
||||
</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
66
DataPRO/FftSharp/Filter.cs
Normal file
66
DataPRO/FftSharp/Filter.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
|
||||
namespace FftSharp
|
||||
{
|
||||
public static class Filter
|
||||
{
|
||||
/// <summary>
|
||||
/// Silence frequencies above the given frequency
|
||||
/// </summary>
|
||||
public static double[] LowPass(double[] values, double sampleRate, double maxFrequency) =>
|
||||
BandPass(values, sampleRate, double.NegativeInfinity, maxFrequency);
|
||||
|
||||
/// <summary>
|
||||
/// Silence frequencies below the given frequency
|
||||
/// </summary>
|
||||
public static double[] HighPass(double[] values, double sampleRate, double minFrequency) =>
|
||||
BandPass(values, sampleRate, minFrequency, double.PositiveInfinity);
|
||||
|
||||
/// <summary>
|
||||
/// Silence frequencies outside the given frequency range
|
||||
/// </summary>
|
||||
public static double[] BandPass(double[] values, double sampleRate, double minFrequency, double maxFrequency)
|
||||
{
|
||||
Complex[] fft = Transform.FFT(values);
|
||||
double[] fftFreqs = Transform.FFTfreq(sampleRate, fft.Length, oneSided: false);
|
||||
for (int i = 0; i < fft.Length; i++)
|
||||
{
|
||||
double freq = Math.Abs(fftFreqs[i]);
|
||||
if ((freq > maxFrequency) || (freq < minFrequency))
|
||||
{
|
||||
fft[i].Real = 0;
|
||||
fft[i].Imaginary = 0;
|
||||
}
|
||||
}
|
||||
return InverseReal(fft);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Silence frequencies inside the given frequency range
|
||||
/// </summary>
|
||||
public static double[] BandStop(double[] values, double sampleRate, double minFrequency, double maxFrequency)
|
||||
{
|
||||
Complex[] fft = Transform.FFT(values);
|
||||
double[] fftFreqs = Transform.FFTfreq(sampleRate, fft.Length, oneSided: false);
|
||||
for (int i = 0; i < fft.Length; i++)
|
||||
{
|
||||
double freq = Math.Abs(fftFreqs[i]);
|
||||
if ((freq <= maxFrequency) && (freq >= minFrequency))
|
||||
{
|
||||
fft[i].Real = 0;
|
||||
fft[i].Imaginary = 0;
|
||||
}
|
||||
}
|
||||
return InverseReal(fft);
|
||||
}
|
||||
|
||||
private static double[] InverseReal(Complex[] fft)
|
||||
{
|
||||
Transform.IFFT(fft);
|
||||
double[] Filtered = new double[fft.Length];
|
||||
for (int i = 0; i < fft.Length; i++)
|
||||
Filtered[i] = fft[i].Real;
|
||||
return Filtered;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
DataPRO/FftSharp/IWindow.cs
Normal file
33
DataPRO/FftSharp/IWindow.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace FftSharp
|
||||
{
|
||||
public interface IWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate this window as a new array with the given length.
|
||||
/// Normalizing will scale the window so the sum of all points is 1.
|
||||
/// </summary>
|
||||
double[] Create(int size, bool normalize = false);
|
||||
|
||||
/// <summary>
|
||||
/// Return a new array where this window was multiplied by the given signal.
|
||||
/// Normalizing will scale the window so the sum of all points is 1 prior to multiplication.
|
||||
/// </summary>
|
||||
double[] Apply(double[] input, bool normalize = false);
|
||||
|
||||
/// <summary>
|
||||
/// Modify the given signal by multiplying it by this window IN PLACE.
|
||||
/// Normalizing will scale the window so the sum of all points is 1 prior to multiplication.
|
||||
/// </summary>
|
||||
void ApplyInPlace(double[] input, bool normalize = false);
|
||||
|
||||
/// <summary>
|
||||
/// Single word name for this window
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A brief description of what makes this window unique and what it is typically used for.
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
}
|
||||
}
|
||||
84
DataPRO/FftSharp/Pad.cs
Normal file
84
DataPRO/FftSharp/Pad.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FftSharp
|
||||
{
|
||||
public static class Pad
|
||||
{
|
||||
/// <summary>
|
||||
/// Test if a number is an even power of 2
|
||||
/// </summary>
|
||||
public static bool IsPowerOfTwo(int x) => ((x & (x - 1)) == 0) && (x > 0);
|
||||
|
||||
/// <summary>
|
||||
/// Return the input array (or a new zero-padded new one) ensuring length is a power of 2
|
||||
/// </summary>
|
||||
/// <param name="input">array of any length</param>
|
||||
/// <returns>the input array or a zero-padded copy</returns>
|
||||
public static Complex[] ZeroPad(Complex[] input)
|
||||
{
|
||||
if (IsPowerOfTwo(input.Length))
|
||||
return input;
|
||||
|
||||
int targetLength = 1;
|
||||
while (targetLength < input.Length)
|
||||
targetLength *= 2;
|
||||
|
||||
int difference = targetLength - input.Length;
|
||||
Complex[] padded = new Complex[targetLength];
|
||||
Array.Copy(input, 0, padded, difference / 2, input.Length);
|
||||
|
||||
return padded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the input array (or a new zero-padded new one) ensuring length is a power of 2
|
||||
/// </summary>
|
||||
/// <param name="input">array of any length</param>
|
||||
/// <returns>the input array or a zero-padded copy</returns>
|
||||
public static double[] ZeroPad(double[] input)
|
||||
{
|
||||
if (IsPowerOfTwo(input.Length))
|
||||
return input;
|
||||
|
||||
int targetLength = 1;
|
||||
while (targetLength < input.Length)
|
||||
targetLength *= 2;
|
||||
|
||||
int difference = targetLength - input.Length;
|
||||
double[] padded = new double[targetLength];
|
||||
Array.Copy(input, 0, padded, difference / 2, input.Length);
|
||||
|
||||
return padded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the input array zero-padded to reach a final length
|
||||
/// </summary>
|
||||
/// <param name="input">array of any length</param>
|
||||
/// <param name="finalLength">pad the array with zeros a the end to achieve this final length</param>
|
||||
/// <returns>a zero-padded copy of the input array</returns>
|
||||
public static Complex[] ZeroPad(Complex[] input, int finalLength)
|
||||
{
|
||||
int difference = finalLength - input.Length;
|
||||
Complex[] padded = new Complex[finalLength];
|
||||
Array.Copy(input, 0, padded, difference / 2, input.Length);
|
||||
return padded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the input array zero-padded to reach a final length
|
||||
/// </summary>
|
||||
/// <param name="input">array of any length</param>
|
||||
/// <param name="finalLength">pad the array with zeros a the end to achieve this final length</param>
|
||||
/// <returns>a zero-padded copy of the input array</returns>
|
||||
public static double[] ZeroPad(double[] input, int finalLength)
|
||||
{
|
||||
int difference = finalLength - input.Length;
|
||||
double[] padded = new double[finalLength];
|
||||
Array.Copy(input, 0, padded, difference / 2, input.Length);
|
||||
return padded;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
DataPRO/FftSharp/Properties/AssemblyInfo.cs
Normal file
36
DataPRO/FftSharp/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("FftSharp")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("FftSharp")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2022")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("9ff2beb4-a267-4139-a37d-9c9a58d7d36d")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
141
DataPRO/FftSharp/SampleData.cs
Normal file
141
DataPRO/FftSharp/SampleData.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace FftSharp
|
||||
{
|
||||
public static class SampleData
|
||||
{
|
||||
public static double[] Times(int sampleRate, int pointCount)
|
||||
{
|
||||
double periodSec = 1.0 / sampleRate;
|
||||
double[] times = new double[pointCount];
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
times[i] = i * periodSec;
|
||||
return times;
|
||||
}
|
||||
|
||||
public static double[] OddSines(int pointCount = 128, int sineCount = 2)
|
||||
{
|
||||
double[] values = new double[pointCount];
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
{
|
||||
for (int s = 0; s < sineCount; s++)
|
||||
{
|
||||
double mult = 1 + 2 * s;
|
||||
values[i] += 1 / mult * Math.Sin(mult * i / Math.PI);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public static void AddSin(double[] data, int sampleRate, double frequency, double magnitude = 1)
|
||||
{
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
data[i] += Math.Sin(i * frequency / sampleRate * 2 * Math.PI) * magnitude;
|
||||
}
|
||||
|
||||
public static void AddOffset(double[] data, double offset = 0)
|
||||
{
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
data[i] += offset;
|
||||
}
|
||||
|
||||
public static void AddWhiteNoise(double[] data, double magnitude = 1, double offset = 0, int? seed = 0)
|
||||
{
|
||||
Random rand = (seed.HasValue) ? new Random(seed.Value) : new Random();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
data[i] += (rand.NextDouble() - .5) * magnitude + offset;
|
||||
}
|
||||
|
||||
public static double[] RandomNormal(int pointCount, double mean = .5, double stdDev = .5, int? seed = 0)
|
||||
{
|
||||
Random rand = (seed.HasValue) ? new Random(seed.Value) : new Random();
|
||||
double[] values = new double[pointCount];
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
double u1 = 1.0 - rand.NextDouble();
|
||||
double u2 = 1.0 - rand.NextDouble();
|
||||
double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2);
|
||||
values[i] = mean + stdDev * randStdNormal;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public static double[] SampleAudio1()
|
||||
{
|
||||
/* This sample data was created to serve as a standard sample to test
|
||||
* this library against other similar libraries, including libraries for
|
||||
* other programming languages.
|
||||
*
|
||||
* These data simulate a sample of audio with the following parameters:
|
||||
* sample rate: 48 kHz
|
||||
* points: 512 (2^9)
|
||||
* offset: 0.1 (above zero)
|
||||
* tone: 2 kHz (amplitude 2)
|
||||
* tone: 10 kHz (amplitude 10)
|
||||
* tone: 20 kHz (amplitude .5)
|
||||
*
|
||||
* The sum of these points should be: 71.52
|
||||
*/
|
||||
|
||||
double[] audio =
|
||||
{
|
||||
0.330, 2.150, 1.440, 1.370, 0.240, 2.600, 3.510, 1.980, 1.880, 0.080,
|
||||
1.820, 1.300, 0.230, -1.160, -1.350, -0.580, -0.840, -1.350, -2.720, -2.530,
|
||||
-0.020, -0.760, -0.480, -2.100, 0.300, 1.860, 1.600, 1.490, 0.580, 2.120,
|
||||
2.790, 1.990, 1.200, 0.800, 2.180, 1.600, -0.370, -1.250, -1.990, 0.350,
|
||||
-1.190, -1.620, -3.280, -2.570, 0.070, -0.810, -1.130, -1.680, -0.250, 1.550,
|
||||
1.080, 1.530, 0.650, 2.530, 2.790, 2.420, 1.720, 0.540, 2.390, 1.510,
|
||||
0.220, -1.420, -1.440, 0.290, -1.610, -1.500, -3.230, -2.200, -0.010, -1.390,
|
||||
-0.470, -1.650, 0.250, 2.050, 1.480, 0.910, 0.760, 2.760, 2.730, 2.450,
|
||||
1.090, 0.280, 2.070, 1.160, 0.270, -1.170, -1.500, 0.200, -0.910, -1.580,
|
||||
-2.460, -2.550, -0.310, -0.940, -1.130, -1.850, 0.420, 1.560, 0.850, 0.880,
|
||||
0.660, 2.730, 3.230, 2.470, 1.120, 0.740, 1.600, 1.730, 0.280, -1.540,
|
||||
-2.180, -0.500, -1.090, -1.390, -2.910, -2.690, -0.160, -1.040, -1.240, -1.520,
|
||||
-0.390, 1.690, 1.520, 0.870, 0.310, 2.750, 3.560, 2.530, 1.290, 0.330,
|
||||
1.810, 1.340, 0.130, -1.580, -2.050, -0.110, -0.850, -1.730, -3.300, -2.100,
|
||||
-0.430, -0.670, -1.340, -1.430, 0.220, 2.160, 1.350, 1.380, 0.210, 2.230,
|
||||
3.210, 1.790, 1.900, 0.380, 1.600, 1.100, 0.440, -1.070, -1.690, -0.090,
|
||||
-0.730, -2.260, -2.890, -2.680, -0.020, -0.960, -0.890, -1.580, 0.270, 2.330,
|
||||
0.970, 0.870, 0.500, 2.520, 2.820, 1.610, 1.130, -0.040, 1.980, 1.280,
|
||||
-0.380, -1.240, -1.520, -0.400, -0.790, -2.310, -2.890, -1.880, 0.160, -1.590,
|
||||
-0.810, -1.860, 0.570, 1.920, 1.440, 1.130, 0.450, 3.020, 3.490, 2.510,
|
||||
1.150, -0.060, 2.430, 1.010, 0.480, -1.090, -1.550, -0.090, -1.350, -1.350,
|
||||
-3.370, -2.150, -0.710, -1.410, -0.970, -1.550, -0.140, 1.640, 0.910, 1.590,
|
||||
0.170, 2.650, 3.160, 2.200, 1.240, -0.170, 1.630, 1.710, 0.310, -0.740,
|
||||
-1.680, -0.350, -1.430, -1.870, -3.200, -1.950, -0.340, -0.970, -1.150, -1.760,
|
||||
-0.160, 2.330, 1.280, 0.810, 1.020, 3.000, 2.760, 2.310, 0.990, -0.000,
|
||||
1.600, 0.940, 0.330, -1.530, -1.490, 0.040, -1.130, -2.100, -2.560, -1.980,
|
||||
-0.390, -0.700, -0.660, -1.670, -0.060, 2.110, 1.090, 1.450, 1.030, 2.650,
|
||||
2.690, 2.160, 1.890, 0.680, 2.070, 0.970, -0.400, -1.080, -1.660, -0.230,
|
||||
-0.830, -2.020, -2.610, -2.320, -0.000, -1.070, -0.940, -1.970, 0.230, 1.890,
|
||||
0.980, 1.060, 0.830, 2.500, 3.520, 1.880, 1.090, -0.040, 2.190, 1.040,
|
||||
0.130, -1.120, -1.560, -0.120, -1.600, -1.900, -3.280, -1.980, -0.270, -0.900,
|
||||
-0.830, -2.120, 0.170, 1.790, 1.660, 0.930, 0.150, 2.320, 3.230, 2.340,
|
||||
1.150, 0.070, 1.550, 1.280, -0.110, -0.790, -1.510, -0.080, -0.750, -2.140,
|
||||
-2.450, -1.990, 0.060, -1.140, -0.620, -1.780, 0.150, 1.640, 1.090, 1.200,
|
||||
0.450, 2.700, 3.200, 2.470, 1.810, 0.110, 2.210, 1.180, 0.070, -0.830,
|
||||
-2.120, 0.300, -1.180, -1.480, -2.450, -2.570, -0.340, -1.280, -1.280, -1.870,
|
||||
0.220, 1.920, 1.580, 1.170, 0.790, 2.830, 2.720, 1.640, 1.510, 0.440,
|
||||
2.100, 1.650, 0.460, -1.390, -1.960, -0.010, -1.040, -2.260, -2.870, -1.850,
|
||||
-0.670, -1.130, -1.400, -1.980, 0.590, 1.370, 1.000, 0.840, 0.550, 2.610,
|
||||
3.460, 1.760, 1.020, -0.040, 2.310, 1.670, 0.350, -1.390, -2.160, -0.480,
|
||||
-1.520, -1.760, -2.670, -2.010, -0.600, -1.210, -1.420, -1.850, 0.080, 1.690,
|
||||
1.270, 1.220, 0.830, 2.230, 2.700, 1.680, 1.420, 0.560, 1.910, 1.550,
|
||||
0.060, -1.550, -1.750, -0.570, -0.920, -1.990, -2.700, -2.130, -0.370, -1.060,
|
||||
-0.630, -1.710, 0.510, 1.740, 1.480, 1.390, 0.780, 2.270, 3.520, 2.130,
|
||||
1.890, -0.140, 2.080, 0.990, 0.570, -1.190, -1.900, 0.320, -1.640, -1.700,
|
||||
-3.090, -1.840, 0.030, -1.150, -0.800, -2.040, 0.590, 2.020, 0.720, 1.690,
|
||||
0.730, 2.380, 3.420, 2.480, 1.420, -0.010, 2.040, 1.220, -0.020, -1.110,
|
||||
-1.950, -0.320, -0.870, -1.550, -2.670, -2.440, -0.300, -1.180, -1.390, -1.800,
|
||||
0.520, 2.110, 1.320, 1.630, 0.270, 2.880, 3.160, 1.990, 1.640, 0.530,
|
||||
2.120, 0.900, -0.220, -1.590, -1.450, 0.050, -1.460, -1.730, -2.760, -2.060,
|
||||
0.100, -1.560, -0.920, -1.600, -0.140, 1.350, 0.830, 0.880, 0.760, 2.300,
|
||||
3.160, 2.110,
|
||||
};
|
||||
|
||||
return audio;
|
||||
}
|
||||
}
|
||||
}
|
||||
527
DataPRO/FftSharp/Transform.cs
Normal file
527
DataPRO/FftSharp/Transform.cs
Normal file
@@ -0,0 +1,527 @@
|
||||
using DTS.Common.Interface;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FftSharp
|
||||
{
|
||||
public static class Transform
|
||||
{
|
||||
/// <summary>
|
||||
/// Compute the discrete Fourier Transform (in-place) using the FFT algorithm.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Data to transform in-place. Length must be a power of 2.</param>
|
||||
public static void FFT(Complex[] buffer)
|
||||
{
|
||||
if (buffer is null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
FFT(buffer.AsSpan());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the discrete Fourier Transform (in-place) using the FFT algorithm.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Data to transform in-place. Length must be a power of 2.</param>
|
||||
public static void FFT(Span<Complex> buffer)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
throw new ArgumentException("Buffer must not be empty");
|
||||
|
||||
if (!IsPowerOfTwo(buffer.Length))
|
||||
throw new ArgumentException("Buffer length must be a power of 2");
|
||||
|
||||
FFT_WithoutChecks(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// High performance FFT function.
|
||||
/// Complex input will be transformed in place.
|
||||
/// Input array length must be a power of two. This length is NOT validated.
|
||||
/// Running on an array with an invalid length may throw an invalid index exception.
|
||||
/// </summary>
|
||||
private static void FFT_WithoutChecks(Span<Complex> buffer)
|
||||
{
|
||||
for (int i = 1; i < buffer.Length; i++)
|
||||
{
|
||||
int j = BitReverse(i, buffer.Length);
|
||||
if (j > i)
|
||||
(buffer[j], buffer[i]) = (buffer[i], buffer[j]);
|
||||
}
|
||||
|
||||
for (int i = 1; i <= buffer.Length / 2; i *= 2)
|
||||
{
|
||||
double mult1 = -Math.PI / i;
|
||||
for (int j = 0; j < buffer.Length; j += (i * 2))
|
||||
{
|
||||
for (int k = 0; k < i; k++)
|
||||
{
|
||||
int evenI = j + k;
|
||||
int oddI = j + k + i;
|
||||
Complex temp = new Complex(Math.Cos(mult1 * k), Math.Sin(mult1 * k));
|
||||
temp *= buffer[oddI];
|
||||
buffer[oddI] = buffer[evenI] - temp;
|
||||
buffer[evenI] += temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the inverse discrete Fourier Transform (in-place) using the FFT algorithm.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Data to transform in-place. Length must be a power of 2.</param>
|
||||
public static void IFFT(Complex[] buffer)
|
||||
{
|
||||
if (buffer is null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
if (buffer.Length == 0)
|
||||
throw new ArgumentException("Buffer must not be empty");
|
||||
|
||||
if (!IsPowerOfTwo(buffer.Length))
|
||||
throw new ArgumentException("Buffer length must be a power of 2");
|
||||
|
||||
IFFT_WithoutChecks(buffer);
|
||||
}
|
||||
|
||||
private static void IFFT_WithoutChecks(Complex[] buffer)
|
||||
{
|
||||
// invert the imaginary component
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
buffer[i] = new Complex(buffer[i].Real, -buffer[i].Imaginary);
|
||||
|
||||
// perform a forward Fourier transform
|
||||
FFT(buffer);
|
||||
|
||||
// invert the imaginary component again and scale the output
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
buffer[i] = new Complex(
|
||||
real: buffer[i].Real / buffer.Length,
|
||||
imaginary: -buffer[i].Imaginary / buffer.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverse the sequence of bits in an integer (01101 -> 10110)
|
||||
/// </summary>
|
||||
private static int BitReverse(int value, int maxValue)
|
||||
{
|
||||
int maxBitCount = (int)Math.Log(maxValue, 2);
|
||||
int output = value;
|
||||
int bitCount = maxBitCount - 1;
|
||||
|
||||
value >>= 1;
|
||||
while (value > 0)
|
||||
{
|
||||
output = (output << 1) | (value & 1);
|
||||
bitCount -= 1;
|
||||
value >>= 1;
|
||||
}
|
||||
|
||||
return (output << bitCount) & ((1 << maxBitCount) - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate sample frequency for each point in a FFT
|
||||
/// </summary>
|
||||
public static double[] FFTfreq(double sampleRate, int pointCount, bool oneSided = true)
|
||||
{
|
||||
double[] freqs = new double[pointCount];
|
||||
|
||||
if (oneSided)
|
||||
{
|
||||
double fftPeriodHz = sampleRate / pointCount / 2;
|
||||
|
||||
// freqs start at 0 and approach maxFreq
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
freqs[i] = i * fftPeriodHz;
|
||||
return freqs;
|
||||
}
|
||||
else
|
||||
{
|
||||
double fftPeriodHz = sampleRate / pointCount;
|
||||
|
||||
// first half: freqs start a 0 and approach maxFreq
|
||||
int halfIndex = pointCount / 2;
|
||||
for (int i = 0; i < halfIndex; i++)
|
||||
freqs[i] = i * fftPeriodHz;
|
||||
|
||||
// second half: then start at -maxFreq and approach 0
|
||||
for (int i = halfIndex; i < pointCount; i++)
|
||||
freqs[i] = -(pointCount - i) * fftPeriodHz;
|
||||
return freqs;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the distance between each FFT point in frequency units (Hz)
|
||||
/// </summary>
|
||||
public static double FFTfreqPeriod(int sampleRate, int pointCount)
|
||||
{
|
||||
return .5 * sampleRate / pointCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if a number is an even power of 2
|
||||
/// </summary>
|
||||
public static bool IsPowerOfTwo(int x)
|
||||
{
|
||||
return ((x & (x - 1)) == 0) && (x > 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an array of Complex data given the real component
|
||||
/// </summary>
|
||||
public static Complex[] MakeComplex(double[] real)
|
||||
{
|
||||
Complex[] com = new Complex[real.Length];
|
||||
MakeComplex(com, real);
|
||||
return com;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an array of Complex data given the real component
|
||||
/// </summary>
|
||||
public static void MakeComplex(Span<Complex> com, Span<double> real)
|
||||
{
|
||||
if (com.Length != real.Length)
|
||||
throw new ArgumentOutOfRangeException("Input length must match");
|
||||
for (int i = 0; i < real.Length; i++)
|
||||
com[i] = new Complex(real[i], 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the 1D discrete Fourier Transform using the Fast Fourier Transform (FFT) algorithm
|
||||
/// </summary>
|
||||
/// <param name="input">real input (must be an array with length that is a power of 2)</param>
|
||||
/// <returns>transformed input</returns>
|
||||
public static Complex[] FFT(double[] input)
|
||||
{
|
||||
if (input is null)
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
if (input.Length == 0)
|
||||
throw new ArgumentException("Input must not be empty");
|
||||
|
||||
if (!IsPowerOfTwo(input.Length))
|
||||
throw new ArgumentException("Input length must be an even power of 2");
|
||||
|
||||
Complex[] buffer = MakeComplex(input);
|
||||
FFT(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the 1D discrete Fourier Transform using the Fast Fourier Transform (FFT) algorithm
|
||||
/// </summary>
|
||||
/// <param name="input">real input (must be an array with length that is a power of 2)</param>
|
||||
/// <returns>real component of transformed input</returns>
|
||||
public static Complex[] RFFT(double[] input)
|
||||
{
|
||||
if (input is null)
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
|
||||
if (input.Length == 0)
|
||||
throw new ArgumentException("Input must not be empty");
|
||||
|
||||
if (!IsPowerOfTwo(input.Length))
|
||||
throw new ArgumentException("Input length must be an even power of 2");
|
||||
|
||||
Complex[] realBuffer = new Complex[input.Length / 2 + 1];
|
||||
RFFT(realBuffer, input);
|
||||
return realBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the 1D discrete Fourier Transform using the Fast Fourier Transform (FFT) algorithm
|
||||
/// </summary>
|
||||
/// <param name="destination">Memory location of the results (must be an equal to input length / 2 + 1)</param>
|
||||
/// <param name="input">real input (must be an array with length that is a power of 2)</param>
|
||||
/// <returns>real component of transformed input</returns>
|
||||
public static void RFFT(Span<Complex> destination, Span<double> input)
|
||||
{
|
||||
if (!IsPowerOfTwo(input.Length))
|
||||
throw new ArgumentException("Input length must be an even power of 2");
|
||||
|
||||
if (destination.Length != input.Length / 2 + 1)
|
||||
throw new ArgumentException("Destination length must be an equal to input length / 2 + 1");
|
||||
|
||||
Complex[] temp = ArrayPool<Complex>.Shared.Rent(input.Length);
|
||||
|
||||
try
|
||||
{
|
||||
Span<Complex> buffer = temp;
|
||||
MakeComplex(buffer, input);
|
||||
FFT(buffer);
|
||||
buffer.Slice(0, destination.Length).CopyTo(destination);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Could not calculate RFFT", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<Complex>.Shared.Return(temp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a Complex array as an array of its absolute values
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static double[] Absolute(Complex[] input)
|
||||
{
|
||||
double[] output = new double[input.Length];
|
||||
for (int i = 0; i < output.Length; i++)
|
||||
output[i] = input[i].Magnitude;
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculte power spectrum density (PSD) original (RMS) units
|
||||
/// </summary>
|
||||
/// <param name="input">real input</param>
|
||||
public static double[] FFTmagnitude(double[] input)
|
||||
{
|
||||
double[] output = new double[input.Length / 2 + 1];
|
||||
FFTmagnitude(output, input);
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculte power spectrum density (PSD) original (RMS) units
|
||||
/// </summary>
|
||||
/// <param name="destination">Memory location of the results.</param>
|
||||
/// <param name="input">real input</param>
|
||||
public static void FFTmagnitude(Span<double> destination, Span<double> input)
|
||||
{
|
||||
if (!IsPowerOfTwo(input.Length))
|
||||
throw new ArgumentException("Input length must be an even power of 2");
|
||||
|
||||
var temp = ArrayPool<Complex>.Shared.Rent(destination.Length);
|
||||
try
|
||||
{
|
||||
var buffer = temp.AsSpan(0, destination.Length);
|
||||
|
||||
// first calculate the FFT
|
||||
RFFT(buffer, input);
|
||||
|
||||
// first point (DC component) is not doubled
|
||||
destination[0] = buffer[0].Magnitude / input.Length;
|
||||
|
||||
// subsequent points are doubled to account for combined positive and negative frequencies
|
||||
for (int i = 1; i < buffer.Length; i++)
|
||||
destination[i] = 2 * buffer[i].Magnitude / input.Length;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Could not calculate FFT magnitude", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<Complex>.Shared.Return(temp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculte power spectrum density (PSD) in dB units
|
||||
/// </summary>
|
||||
/// <param name="input">real input</param>
|
||||
public static double[] FFTpower(double[] input)
|
||||
{
|
||||
if (!IsPowerOfTwo(input.Length))
|
||||
throw new ArgumentException("Input length must be an even power of 2");
|
||||
|
||||
double[] output = FFTmagnitude(input);
|
||||
Parallel.For(0, output.Length, i => output[i] = 2 * 10 * Math.Log10(output[i]));
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculte power spectrum density (PSD) in dB units
|
||||
/// </summary>
|
||||
/// <param name="destination">Memory location of the results.</param>
|
||||
/// <param name="input">real input</param>
|
||||
public static void FFTpower(Span<double> destination, double[] input)
|
||||
{
|
||||
if (!IsPowerOfTwo(input.Length))
|
||||
throw new ArgumentException("Input length must be an even power of 2");
|
||||
|
||||
FFTmagnitude(destination, input);
|
||||
for (int i = 0; i < destination.Length; i++)
|
||||
destination[i] = 2 * 10 * Math.Log10(destination[i]);
|
||||
}
|
||||
|
||||
public static double[] PSD_Welch(double[] input, long sampleRate, WindowType windowType, int windowWidth, int overlapPct, WindowAveragingType averagingType, SetReadCalcProgressValueDelegate SetProgress = null)
|
||||
{
|
||||
if (!IsPowerOfTwo(windowWidth))
|
||||
throw new ArgumentException("Window width must be an even power of 2");
|
||||
object counter_lock = new object();
|
||||
var completed = 0;
|
||||
var progress = 0D;
|
||||
|
||||
// "The signal is split up into overlapping segments: the original data segment is split up into L data segments of length M, overlapping by D points"
|
||||
var overlapWidth = (int)(overlapPct / 100D * windowWidth);
|
||||
var step = windowWidth - overlapWidth;
|
||||
|
||||
double[][] segments = new double[input.Length / step][];
|
||||
if (null != SetProgress) SetProgress(string.Format(DTS.Common.Strings.Strings.GeneratingPSD, DTS.Common.Strings.Strings.GeneratingPSD_CreatingSegments), 0D);
|
||||
Parallel.For(0, input.Length / step, index =>
|
||||
{
|
||||
var i = index * step;
|
||||
var width = i + windowWidth < input.Length ? windowWidth : input.Length - i;
|
||||
var newSegment = new double[windowWidth];
|
||||
Array.Copy(input, i, newSegment, 0, width);
|
||||
segments[i / step] = newSegment;
|
||||
lock (counter_lock)
|
||||
{
|
||||
completed++;
|
||||
var pct = (double)completed / (input.Length / step) * 100;
|
||||
if (Math.Floor(pct) > progress)
|
||||
{
|
||||
progress = pct;
|
||||
if (null != SetProgress) SetProgress(String.Empty, progress);//only update on whole % changes
|
||||
}
|
||||
}
|
||||
});
|
||||
if (null != SetProgress) SetProgress(string.Empty, 100D);
|
||||
|
||||
// "After the data is split up into overlapping segments, the individual L data segments have a window applied to them"
|
||||
if (null != SetProgress) SetProgress(string.Format(DTS.Common.Strings.Strings.GeneratingPSD, DTS.Common.Strings.Strings.GeneratingPSD_ApplyingWindows), 0D);
|
||||
var window = Window.GetWindow(windowType);
|
||||
window.Create(segments[0].Length, false);
|
||||
completed = 0;
|
||||
progress = 0D;
|
||||
Parallel.For(0, segments.Length, i =>
|
||||
{
|
||||
window.ApplyInPlace(segments[i], false);
|
||||
lock (counter_lock)
|
||||
{
|
||||
completed++;
|
||||
var pct = (double)completed / segments.Length * 100;
|
||||
if (Math.Floor(pct) > progress)
|
||||
{
|
||||
progress = pct;
|
||||
if (null != SetProgress) SetProgress(String.Empty, progress);//only update on whole % changes
|
||||
}
|
||||
}
|
||||
});
|
||||
if (null != SetProgress) SetProgress(string.Empty, 100D);
|
||||
|
||||
// "After doing the above, the periodogram is calculated by computing the discrete Fourier transform...
|
||||
if (null != SetProgress) SetProgress(string.Format(DTS.Common.Strings.Strings.GeneratingPSD, DTS.Common.Strings.Strings.GeneratingPSD_CalculatingFFTs), 0D);
|
||||
completed = 0;
|
||||
progress = 0D;
|
||||
Complex[][] windowFFTs = new Complex[segments.Length][];
|
||||
Parallel.For(0, segments.Length, i =>
|
||||
{
|
||||
windowFFTs[i] = RFFT(segments[i]);
|
||||
lock (counter_lock)
|
||||
{
|
||||
completed++;
|
||||
var pct = (double)completed / segments.Length * 100;
|
||||
if (Math.Floor(pct) > progress)
|
||||
{
|
||||
progress = pct;
|
||||
if (null != SetProgress) SetProgress(String.Empty, progress);//only update on whole % changes
|
||||
}
|
||||
}
|
||||
});
|
||||
if (null != SetProgress) SetProgress(string.Empty, 100D);
|
||||
|
||||
//...and then computing the squared magnitude of the result.
|
||||
// The individual periodograms are then averaged, which reduces the variance of the individual power measurements. The end result is an array of power measurements vs. frequency 'bin'."
|
||||
if (null != SetProgress) SetProgress(string.Format(DTS.Common.Strings.Strings.GeneratingPSD, DTS.Common.Strings.Strings.GeneratingPSD_CalculatingResults), 0D);
|
||||
var scale = sampleRate * windowWidth;
|
||||
var denominator = sampleRate * segments.Length * windowWidth;
|
||||
var results = new double[windowFFTs[0].Length];
|
||||
|
||||
progress = 0D;
|
||||
Parallel.For(0, results.Length, i =>
|
||||
{
|
||||
switch (averagingType)
|
||||
{
|
||||
case WindowAveragingType.PeakHoldMax:
|
||||
var max = windowFFTs.Max(fft => fft[i].MagnitudeSquared * 2);
|
||||
results[i] = Math.Abs(max / scale);
|
||||
break;
|
||||
case WindowAveragingType.PeakHoldMin:
|
||||
var min = windowFFTs.Where(fft => fft[i].MagnitudeSquared != 0).Min(fft => fft[i].MagnitudeSquared * 2);
|
||||
results[i] = Math.Abs(min / scale);
|
||||
break;
|
||||
case WindowAveragingType.Averaging:
|
||||
default:
|
||||
var numerator = windowFFTs.Sum(fft => fft[i].MagnitudeSquared * 2);
|
||||
results[i] = Math.Abs(numerator / denominator);
|
||||
break;
|
||||
}
|
||||
lock (counter_lock)
|
||||
{
|
||||
completed++;
|
||||
var pct = (double)completed / segments.Length * 100;
|
||||
if (Math.Floor(pct) > progress)
|
||||
{
|
||||
progress = pct;
|
||||
if (null != SetProgress) SetProgress(String.Empty, progress);//only update on whole % changes
|
||||
}
|
||||
}
|
||||
});
|
||||
if (null != SetProgress) SetProgress(string.Empty, 100D);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static double MelToFreq(double mel)
|
||||
{
|
||||
return 700 * (Math.Pow(10, mel / 2595d) - 1);
|
||||
}
|
||||
|
||||
public static double MelFromFreq(double frequencyHz)
|
||||
{
|
||||
return 2595 * Math.Log10(1 + frequencyHz / 700);
|
||||
}
|
||||
|
||||
public static double[] MelScale(double[] fft, int sampleRate, int melBinCount)
|
||||
{
|
||||
double freqMax = sampleRate / 2;
|
||||
double maxMel = MelFromFreq(freqMax);
|
||||
|
||||
double[] fftMel = new double[melBinCount];
|
||||
double melPerBin = maxMel / (melBinCount + 1);
|
||||
for (int binIndex = 0; binIndex < melBinCount; binIndex++)
|
||||
{
|
||||
double melLow = melPerBin * binIndex;
|
||||
double melHigh = melPerBin * (binIndex + 2);
|
||||
|
||||
double freqLow = MelToFreq(melLow);
|
||||
double freqHigh = MelToFreq(melHigh);
|
||||
|
||||
int indexLow = (int)(fft.Length * freqLow / freqMax);
|
||||
int indexHigh = (int)(fft.Length * freqHigh / freqMax);
|
||||
int indexSpan = indexHigh - indexLow;
|
||||
|
||||
double binScaleSum = 0;
|
||||
for (int i = 0; i < indexSpan; i++)
|
||||
{
|
||||
double binFrac = (double)i / indexSpan;
|
||||
double indexScale = (binFrac < .5) ? binFrac * 2 : 1 - binFrac;
|
||||
binScaleSum += indexScale;
|
||||
fftMel[binIndex] += fft[indexLow + i] * indexScale;
|
||||
}
|
||||
fftMel[binIndex] /= binScaleSum;
|
||||
}
|
||||
|
||||
return fftMel;
|
||||
}
|
||||
|
||||
public static T[] SubArray<T>(this T[] array, int offset, int length)
|
||||
{
|
||||
T[] result = new T[length];
|
||||
Array.Copy(array, offset, result, 0, length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
198
DataPRO/FftSharp/Window.cs
Normal file
198
DataPRO/FftSharp/Window.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FftSharp
|
||||
{
|
||||
public enum WindowType
|
||||
{
|
||||
Bartlett,
|
||||
Blackman,
|
||||
BlackmanHarris,
|
||||
Cosine,
|
||||
FlatTop,
|
||||
Hamming,
|
||||
Hanning,
|
||||
Kaiser,
|
||||
Rectangular,
|
||||
Tukey,
|
||||
Welch
|
||||
}
|
||||
public enum WindowAveragingType
|
||||
{
|
||||
Averaging,
|
||||
PeakHoldMax,
|
||||
PeakHoldMin
|
||||
}
|
||||
public abstract class Window : IWindow
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
|
||||
public abstract string Description { get; }
|
||||
|
||||
public override string ToString() => Name;
|
||||
|
||||
protected abstract double windowValue(int index, int size);
|
||||
public virtual double[] Create(int size, bool normalize = false)
|
||||
{
|
||||
// save this window so it can be re-used if the next request is the same size
|
||||
if (lastWindow.Length == size && lastWindowNormalize == normalize)
|
||||
{
|
||||
return lastWindow;
|
||||
}
|
||||
if (size <= 0)
|
||||
{
|
||||
return new double[0];
|
||||
}
|
||||
double[] window = new double[size];
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
window[i] = windowValue(i, size);
|
||||
|
||||
if (normalize)
|
||||
NormalizeInPlace(window);
|
||||
lastWindow = window;
|
||||
lastWindowNormalize = normalize;
|
||||
return window;
|
||||
}
|
||||
|
||||
protected double[] lastWindow = new double[0];
|
||||
protected bool lastWindowNormalize = false;
|
||||
|
||||
/// <summary>
|
||||
/// Multiply the array by this window and return the result as a new array
|
||||
/// </summary>
|
||||
public double[] Apply(double[] input, bool normalize = false)
|
||||
{
|
||||
double[] window = Create(input.Length, normalize);
|
||||
double[] output = new double[input.Length];
|
||||
|
||||
Parallel.For(0, input.Length, i => output[i] = input[i] * window[i]);
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiply the array by this window, modifying it in place
|
||||
/// </summary>
|
||||
public void ApplyInPlace(double[] input, bool normalize = false)
|
||||
{
|
||||
double[] window = Create(input.Length, normalize);
|
||||
|
||||
Parallel.For(0, input.Length, i => input[i] = input[i] * window[i]);
|
||||
}
|
||||
|
||||
internal static void NormalizeInPlace(double[] values)
|
||||
{
|
||||
double sum = 0;
|
||||
Parallel.For(0, values.Length, i => sum += values[i]);
|
||||
|
||||
Parallel.For(0, values.Length, i => values[i] /= sum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return an array containing all available windows.
|
||||
/// Note that all windows returned will use the default constructor, but some
|
||||
/// windows have customization options in their constructors if you create them individually.
|
||||
/// </summary>
|
||||
public static IWindow[] GetWindows()
|
||||
{
|
||||
return Assembly.GetExecutingAssembly()
|
||||
.GetTypes()
|
||||
.Where(x => x.IsClass)
|
||||
.Where(x => !x.IsAbstract)
|
||||
.Where(x => x.GetInterfaces().Contains(typeof(IWindow)))
|
||||
.Select(x => (IWindow)Activator.CreateInstance(x))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static IWindow GetWindow(WindowType type)
|
||||
{
|
||||
return GetWindows().First(w => type.ToString() == w.Name);
|
||||
}
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static double[] Rectangular(int pointCount) => new Windows.Rectangular().Create(pointCount);
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static double[] Hanning(int pointCount) => new Windows.Hanning().Create(pointCount);
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static double[] Hamming(int pointCount) => new Windows.Hanning().Create(pointCount);
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static double[] Blackman(int pointCount) => new Windows.Blackman().Create(pointCount);
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static double[] BlackmanCustom(int pointCount, double a = .42, double b = .5, double c = .08) => new Windows.Blackman(a, b, c).Create(pointCount);
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static double[] BlackmanHarris(int pointCount) => new Windows.Blackman(0.42323, 0.49755, 0.07922).Create(pointCount);
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static double[] FlatTop(int pointCount) => new Windows.FlatTop().Create(pointCount);
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static double[] Bartlett(int pointCount) => new Windows.Bartlett().Create(pointCount);
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static double[] Cosine(int pointCount) => new Windows.Cosine().Create(pointCount);
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static double[] Kaiser(int pointCount, double beta) => new Windows.Kaiser(beta).Create(pointCount);
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static double[] Apply(double[] window, double[] signal)
|
||||
{
|
||||
if (window.Length != signal.Length)
|
||||
throw new ArgumentException("window and signal must be same length");
|
||||
|
||||
double[] output = new double[window.Length];
|
||||
|
||||
for (int i = 0; i < signal.Length; i++)
|
||||
output[i] = signal[i] * window[i];
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
[Obsolete("This method is obsolete. Create a window in the Windows namespace and interact with its methods.")]
|
||||
public static void ApplyInPlace(double[] window, double[] signal)
|
||||
{
|
||||
if (window.Length != signal.Length)
|
||||
throw new ArgumentException("window and signal must be same length");
|
||||
|
||||
Parallel.For(0, signal.Length, i => signal[i] = signal[i] * window[i]);
|
||||
}
|
||||
|
||||
[Obsolete("Use GetWindows() instead")]
|
||||
public static string[] GetWindowNames()
|
||||
{
|
||||
return typeof(Window)
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(x => x.ReturnType.Equals(typeof(double[])))
|
||||
.Where(x => x.GetParameters().Length == 1)
|
||||
.Where(x => x.GetParameters()[0].ParameterType == typeof(int))
|
||||
.Select(x => x.Name)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
[Obsolete("Use GetWindows() and work with the output instead")]
|
||||
public static double[] GetWindowByName(string windowName, int pointCount)
|
||||
{
|
||||
MethodInfo[] windowInfos = typeof(Window)
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.Where(x => x.ReturnType.Equals(typeof(double[])))
|
||||
.Where(x => x.GetParameters().Length == 1)
|
||||
.Where(x => x.GetParameters()[0].ParameterType == typeof(int))
|
||||
.Where(x => x.Name == windowName)
|
||||
.ToArray();
|
||||
|
||||
if (windowInfos.Length == 0)
|
||||
throw new ArgumentException($"invalid window name: {windowName}");
|
||||
|
||||
object[] parameters = new object[] { pointCount };
|
||||
double[] result = (double[])windowInfos[0].Invoke(null, parameters);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
DataPRO/FftSharp/Windows/Bartlett.cs
Normal file
17
DataPRO/FftSharp/Windows/Bartlett.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace FftSharp.Windows
|
||||
{
|
||||
public class Bartlett : Window, IWindow
|
||||
{
|
||||
public override string Name => "Bartlett–Hann";
|
||||
public override string Description =>
|
||||
"The Bartlett–Hann window is triangular in shape (a 2nd order B-spline) which is effectively the " +
|
||||
"convolution of two half-sized rectangular windows.";
|
||||
|
||||
protected override double windowValue(int index, int size)
|
||||
{
|
||||
return 1 - Math.Abs((double)(index - (size / 2)) / (size / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
29
DataPRO/FftSharp/Windows/Blackman.cs
Normal file
29
DataPRO/FftSharp/Windows/Blackman.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace FftSharp.Windows
|
||||
{
|
||||
public class Blackman : Window, IWindow
|
||||
{
|
||||
private readonly double A = 0.42659071;
|
||||
private readonly double B = 0.49656062;
|
||||
private readonly double C = 0.07684867;
|
||||
|
||||
public override string Name => "Blackman";
|
||||
public override string Description =>
|
||||
"These exact values place zeros at the third and fourth sidelobes, " +
|
||||
"but result in a discontinuity at the edges and a 6 dB/oct fall-off. " +
|
||||
"The truncated coefficients do not null the sidelobes as well, but have an improved 18 dB/oct fall-off.";
|
||||
|
||||
public Blackman() { }
|
||||
|
||||
public Blackman(double a, double b, double c) : this()
|
||||
{
|
||||
(A, B, C) = (a, b, c);
|
||||
}
|
||||
|
||||
protected override double windowValue(int index, int size)
|
||||
{
|
||||
return A - B * Math.Cos(2 * Math.PI * index / size) + C * Math.Cos(4 * Math.PI * index / size);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
DataPRO/FftSharp/Windows/BlackmanHarris.cs
Normal file
29
DataPRO/FftSharp/Windows/BlackmanHarris.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace FftSharp.Windows
|
||||
{
|
||||
public class BlackmanHarris : Window, IWindow
|
||||
{
|
||||
private readonly double A = 0.35875;
|
||||
private readonly double B = 0.48829;
|
||||
private readonly double C = 0.14128;
|
||||
private readonly double D = 0.01168;
|
||||
|
||||
public override string Name => "Blackman-Harris";
|
||||
public override string Description =>
|
||||
"The Blackman-Harris window is similar to Hamming and Hanning windows. " +
|
||||
"The resulting spectrum has a wide peak, but good side lobe compression.";
|
||||
|
||||
public BlackmanHarris() { }
|
||||
|
||||
public BlackmanHarris(double a, double b, double c, double d) : this()
|
||||
{
|
||||
(A, B, C, D) = (a, b, c, d);
|
||||
}
|
||||
|
||||
protected override double windowValue(int index, int size)
|
||||
{
|
||||
return A - B * Math.Cos(2 * Math.PI * index / size) + C * Math.Cos(4 * Math.PI * index / size) - D * Math.Cos(6 * Math.PI * index / size);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
DataPRO/FftSharp/Windows/Cosine.cs
Normal file
17
DataPRO/FftSharp/Windows/Cosine.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace FftSharp.Windows
|
||||
{
|
||||
public class Cosine : Window, IWindow
|
||||
{
|
||||
public override string Name => "Cosine";
|
||||
public override string Description =>
|
||||
"This window is simply a cosine function. It reaches zero on both sides and is similar to " +
|
||||
"Blackman, Hamming, Hanning, and flat top windows, but probably should not be used in practice.";
|
||||
|
||||
protected override double windowValue(int index, int size)
|
||||
{
|
||||
return Math.Sin(index * Math.PI / (size - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
22
DataPRO/FftSharp/Windows/FlatTop.cs
Normal file
22
DataPRO/FftSharp/Windows/FlatTop.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FftSharp.Windows
|
||||
{
|
||||
public class FlatTop : Blackman
|
||||
{
|
||||
public override string Name => "FlatTop";
|
||||
public override string Description =>
|
||||
"A flat top window is a partially negative-valued window that has minimal scalloping loss in the frequency domain. " +
|
||||
"These properties are desirable for the measurement of amplitudes of sinusoidal frequency components. " +
|
||||
"Drawbacks of the broad bandwidth are poor frequency resolution and high noise bandwidth. " +
|
||||
"The flat top window crosses the zero line causing a broader peak in the frequency domain, " +
|
||||
"which is closer to the true amplitude of the signal than with other windows";
|
||||
|
||||
public FlatTop() : base(0.2810639, 0.5208972, 0.1980399)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
18
DataPRO/FftSharp/Windows/Hamming.cs
Normal file
18
DataPRO/FftSharp/Windows/Hamming.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace FftSharp.Windows
|
||||
{
|
||||
public class Hamming : Window, IWindow
|
||||
{
|
||||
public override string Name => "Hamming";
|
||||
public override string Description =>
|
||||
"The Hamming window has a sinusoidal shape does NOT touch zero at the edges (unlike the similar Hanning window). " +
|
||||
"It is similar to the Hanning window but its abrupt edges are designed to cancel the largest side lobe. " +
|
||||
"It may be a good choice for low-quality (8-bit) auto where side lobes lie beyond the quantization noise floor.";
|
||||
|
||||
protected override double windowValue(int index, int size)
|
||||
{
|
||||
return 0.54 - 0.46 * Math.Cos(2 * Math.PI * index / size);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
DataPRO/FftSharp/Windows/Hanning.cs
Normal file
18
DataPRO/FftSharp/Windows/Hanning.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace FftSharp.Windows
|
||||
{
|
||||
public class Hanning : Window, IWindow
|
||||
{
|
||||
public override string Name => "Hanning";
|
||||
public override string Description =>
|
||||
"The Hanning window has a sinusoidal shape which touches zero at the edges (unlike the similar Hamming window). " +
|
||||
"It has good frequency resolution, low spectral leakage, and is satisfactory for 95 percent of use cases. " +
|
||||
"If you do not know the nature of the signal but you want to apply a smoothing window, start with the Hann window.";
|
||||
|
||||
protected override double windowValue(int index, int size)
|
||||
{
|
||||
return 0.5 - 0.5 * Math.Cos(2 * Math.PI * index / size);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
DataPRO/FftSharp/Windows/Kaiser.cs
Normal file
54
DataPRO/FftSharp/Windows/Kaiser.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
|
||||
namespace FftSharp.Windows
|
||||
{
|
||||
public class Kaiser : Window, IWindow
|
||||
{
|
||||
public readonly double Beta;
|
||||
public override string Name => $"Kaiser-Bessel";
|
||||
public override string Description =>
|
||||
"A Kaiser-Bessel window strikes a balance among the various conflicting goals of amplitude " +
|
||||
"accuracy, side lobe distance, and side lobe height. It compares roughly to the BlackmanHarris window functions, " +
|
||||
"but for the same main lobe width, the near side lobes tend to be higher, but the further out side lobes are lower. " +
|
||||
"Choosing this window often reveals signals close to the noise floor";
|
||||
|
||||
public Kaiser()
|
||||
{
|
||||
Beta = 15;
|
||||
}
|
||||
|
||||
public Kaiser(double beta)
|
||||
{
|
||||
Beta = beta;
|
||||
}
|
||||
|
||||
public static double I0(double x)
|
||||
{
|
||||
// Derived from code workby oygx210/navguide:
|
||||
// https://github.com/oygx210/navguide/blob/master/src/common/bessel.c
|
||||
|
||||
double ax = Math.Abs(x);
|
||||
if (ax < 3.75)
|
||||
{
|
||||
double y = Math.Pow(x / 3.75, 2);
|
||||
double[] m = { 3.5156229, 3.0899424, 1.2067492, 0.2659732, 0.360768e-1, 0.45813e-2 };
|
||||
return 1.0 + y * (m[0] + y * (m[1] + y * (m[2] + y * (m[3] + y * (m[4] + y * m[5])))));
|
||||
}
|
||||
else
|
||||
{
|
||||
double y = 3.75 / ax;
|
||||
double[] m = { 0.39894228, 0.1328592e-1, 0.225319e-2, -0.157565e-2, 0.916281e-2, -0.2057706e-1, 0.2635537e-1, -0.1647633e-1, 0.392377e-2 };
|
||||
return (Math.Exp(ax) / Math.Sqrt(ax)) * (m[0] + y * (m[1] + y * (m[2] + y * (m[3] + y * (m[4] + y * (m[5] + y * (m[6] + y * (m[7] + y * m[8]))))))));
|
||||
}
|
||||
}
|
||||
|
||||
protected override double windowValue(int index, int size)
|
||||
{
|
||||
int M = size;
|
||||
double alpha = (M - 1) / 2.0;
|
||||
|
||||
|
||||
return I0(Beta * Math.Sqrt(1 - Math.Pow((index - alpha) / alpha, 2))) / I0(Beta);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
DataPRO/FftSharp/Windows/Rectangular.cs
Normal file
16
DataPRO/FftSharp/Windows/Rectangular.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace FftSharp.Windows
|
||||
{
|
||||
public class Rectangular : Window, IWindow
|
||||
{
|
||||
public override string Name => "Rectangular";
|
||||
public override string Description =>
|
||||
"The rectangular window (sometimes known as the boxcar or Dirichlet window) is the simplest window, " +
|
||||
"equivalent to replacing all but N values of a data sequence by zeros, making it appear as though " +
|
||||
"the waveform suddenly turns on and off. This window preserves transients at the start and end of the signal.";
|
||||
|
||||
protected override double windowValue(int index, int size)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
DataPRO/FftSharp/Windows/Tukey.cs
Normal file
34
DataPRO/FftSharp/Windows/Tukey.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace FftSharp.Windows
|
||||
{
|
||||
public class Tukey : Window, IWindow
|
||||
{
|
||||
private readonly double Alpha;
|
||||
|
||||
public override string Name => "Tukey";
|
||||
public override string Description =>
|
||||
"A Tukey window has a flat center and tapers at the edges according to a cosine function. " +
|
||||
"The amount of taper is defined by alpha (with low values being less taper). " +
|
||||
"Tukey windows are ideal for analyzing transient data since the amplitude of transient signal " +
|
||||
"in the time domain is less likely to be altered compared to using Hanning or flat top.";
|
||||
|
||||
public Tukey()
|
||||
{
|
||||
Alpha = .5;
|
||||
}
|
||||
|
||||
public Tukey(double alpha = .5)
|
||||
{
|
||||
Alpha = alpha;
|
||||
}
|
||||
protected override double windowValue(int index, int size)
|
||||
{
|
||||
double m = 2 * Math.PI / (Alpha * size);
|
||||
int edgeSizePoints = (int)(size * Alpha / 2);
|
||||
bool isEdge = (index < edgeSizePoints) || (index > size - edgeSizePoints);
|
||||
|
||||
return isEdge ? (1 - Math.Cos(index * m)) / 2 : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
DataPRO/FftSharp/Windows/Welch.cs
Normal file
18
DataPRO/FftSharp/Windows/Welch.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace FftSharp.Windows
|
||||
{
|
||||
public class Welch : Window, IWindow
|
||||
{
|
||||
public override string Name => "Welch";
|
||||
public override string Description =>
|
||||
"The Welch window is typically used for antialiasing and resampling. " +
|
||||
"Its frequency response is better than that of the Bartlett windowed cosc function below pi, " +
|
||||
"but it shows again a rather distinctive bump above.";
|
||||
|
||||
protected override double windowValue(int index, int size)
|
||||
{
|
||||
double halfN = (size - 1) / 2.0;
|
||||
double b = (index - halfN) / halfN;
|
||||
return 1 - b * b;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
DataPRO/FftSharp/icon.png
Normal file
BIN
DataPRO/FftSharp/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.8", FrameworkDisplayName = "")]
|
||||
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user