Initial commit — WPF guitar note name tutor using FFT frequency detection

This commit is contained in:
2026-05-09 03:17:07 +00:00
commit 99b73f710a
30 changed files with 1637 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
using System;
namespace SoundAnalysis
{
/// <summary>
/// Complex number.
/// </summary>
struct ComplexNumber
{
public double Re;
public double Im;
public ComplexNumber(double re)
{
Re = re;
Im = 0;
}
public ComplexNumber(double re, double im)
{
Re = re;
Im = im;
}
public static ComplexNumber operator *(ComplexNumber n1, ComplexNumber n2)
{
return new ComplexNumber(n1.Re * n2.Re - n1.Im * n2.Im,
n1.Im * n2.Re + n1.Re * n2.Im);
}
public static ComplexNumber operator +(ComplexNumber n1, ComplexNumber n2)
{
return new ComplexNumber(n1.Re + n2.Re, n1.Im + n2.Im);
}
public static ComplexNumber operator -(ComplexNumber n1, ComplexNumber n2)
{
return new ComplexNumber(n1.Re - n2.Re, n1.Im - n2.Im);
}
public static ComplexNumber operator -(ComplexNumber n)
{
return new ComplexNumber(-n.Re, -n.Im);
}
public static implicit operator ComplexNumber(double n)
{
return new ComplexNumber(n, 0);
}
public ComplexNumber PoweredE()
{
double e = Math.Exp(Re);
return new ComplexNumber(e * Math.Cos(Im), e * Math.Sin(Im));
}
public double Power2()
{
return Re * Re - Im * Im;
}
public double AbsPower2()
{
return Re * Re + Im * Im;
}
public override string ToString()
{
return String.Format("{0}+i*{1}", Re, Im);
}
}
}
+121
View File
@@ -0,0 +1,121 @@
using System;
namespace SoundAnalysis
{
/// <summary>
/// Cooley-Tukey FFT algorithm.
/// </summary>
public static class FftAlgorithm
{
/// <summary>
/// Calculates FFT using Cooley-Tukey FFT algorithm.
/// </summary>
/// <param name="x">input data</param>
/// <returns>spectrogram of the data</returns>
/// <remarks>
/// If amount of data items not equal a power of 2, then algorithm
/// automatically pad with 0s to the lowest amount of power of 2.
/// </remarks>
public static double[] Calculate(double[] x)
{
int length;
int bitsInLength;
if (IsPowerOfTwo(x.Length))
{
length = x.Length;
bitsInLength = Log2(length) - 1;
}
else
{
bitsInLength = Log2(x.Length);
length = 1 << bitsInLength;
// the items will be pad with zeros
}
// bit reversal
ComplexNumber[] data = new ComplexNumber[length];
for (int i = 0; i < x.Length; i++)
{
int j = ReverseBits(i, bitsInLength);
data[j] = new ComplexNumber(x[i]);
}
// Cooley-Turkey
for (int i = 0; i < bitsInLength; i++)
{
int m = 1 << i;
int n = m * 2;
double alpha = -(2 * Math.PI / n);
for (int k = 0; k < m; k++)
{
// e^(-2*pi*i/N*k)
ComplexNumber oddPartMultiplier = new ComplexNumber(0, alpha * k).PoweredE();
for (int j = k; j < length; j += n)
{
ComplexNumber evenPart = data[j];
ComplexNumber oddPart = oddPartMultiplier * data[j + m];
data[j] = evenPart + oddPart;
data[j + m] = evenPart - oddPart;
}
}
}
// calculate spectrogram
double[] spectrogram = new double[length];
for (int i = 0; i < spectrogram.Length; i++)
{
spectrogram[i] = data[i].AbsPower2();
}
return spectrogram;
}
/// <summary>
/// Gets number of significat bytes.
/// </summary>
/// <param name="n">Number</param>
/// <returns>Amount of minimal bits to store the number.</returns>
private static int Log2(int n)
{
int i = 0;
while (n > 0)
{
++i; n >>= 1;
}
return i;
}
/// <summary>
/// Reverses bits in the number.
/// </summary>
/// <param name="n">Number</param>
/// <param name="bitsCount">Significant bits in the number.</param>
/// <returns>Reversed binary number.</returns>
private static int ReverseBits(int n, int bitsCount)
{
int reversed = 0;
for (int i = 0; i < bitsCount; i++)
{
int nextBit = n & 1;
n >>= 1;
reversed <<= 1;
reversed |= nextBit;
}
return reversed;
}
/// <summary>
/// Checks if number is power of 2.
/// </summary>
/// <param name="n">number</param>
/// <returns>true if n=2^k and k is positive integer</returns>
private static bool IsPowerOfTwo(int n)
{
return n > 1 && (n & (n - 1)) == 0;
}
}
}
+36
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("SoundAnalysis")]
[assembly: AssemblyDescription("Sound anslysis library: FFT")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("notmasteryet")]
[assembly: AssemblyProduct("FftGuitarTuner")]
[assembly: AssemblyCopyright("Copyright © notmasteryet 2009")]
[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("52c3b811-84a7-4a5b-92e6-64f3c16752bb")]
// 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")]
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.21022</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{ABA54DC3-324B-49DE-B79E-C4F573306E4F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SoundAnalysis</RootNamespace>
<AssemblyName>SoundAnalysis</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="ComplexNumber.cs" />
<Compile Include="FftCalc.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>