This commit is contained in:
2026-04-17 14:55:32 -04:00
commit bc3ac1d4c9
18017 changed files with 4371742 additions and 0 deletions

106
DataPRO/FftSharp/Complex.cs Normal file
View 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;
}
}
}

View 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);
}
}
}

View 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>

View 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>

View 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;
}
}
}

View 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
View 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;
}
}
}

View 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")]

View 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;
}
}
}

View 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
View 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;
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace FftSharp.Windows
{
public class Bartlett : Window, IWindow
{
public override string Name => "BartlettHann";
public override string Description =>
"The BartlettHann 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));
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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));
}
}
}

View 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)
{
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.8", FrameworkDisplayName = "")]

View File

@@ -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")]