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
+37
View File
@@ -0,0 +1,37 @@
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("SoundCapture")]
[assembly: AssemblyDescription("Sound capture library")]
[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("b12d1bc8-57d3-464f-9286-b1456655872a")]
// 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")]
+54
View File
@@ -0,0 +1,54 @@
<?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.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{DAE12676-B26C-4487-A1A5-D7FE2E64E6CE}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SoundCapture</RootNamespace>
<AssemblyName>SoundCapture</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="Microsoft.DirectX.DirectSound, Version=1.0.2902.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="SoundCaptureBase.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SoundCaptureDevice.cs" />
<Compile Include="SoundCaptureException.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>
+194
View File
@@ -0,0 +1,194 @@
using System;
using System.Threading;
using Microsoft.DirectX.DirectSound;
using Microsoft.Win32.SafeHandles;
namespace SoundCapture
{
/// <summary>
/// Base class to capture audio samples.
/// </summary>
public abstract class SoundCaptureBase : IDisposable
{
const int BufferSeconds = 3;
const int NotifyPointsInSecond = 2;
// change in next two will require also code change
const int BitsPerSample = 16;
const int ChannelCount = 1;
int _sampleRate = 44100;
bool _disposed;
private bool IsCapturing { get; set; }
protected int SampleRate
{
get { return _sampleRate; }
set
{
if (_sampleRate <= 0) throw new ArgumentOutOfRangeException();
EnsureIdle();
_sampleRate = value;
}
}
Capture _capture;
CaptureBuffer _buffer;
Notify _notify;
int _bufferLength;
readonly AutoResetEvent _positionEvent;
readonly SafeWaitHandle _positionEventHandle;
readonly ManualResetEvent _terminated;
Thread _thread;
readonly SoundCaptureDevice _device;
protected SoundCaptureBase()
: this(SoundCaptureDevice.GetDefaultDevice())
{
}
protected SoundCaptureBase(SoundCaptureDevice device)
{
_device = device;
_positionEvent = new AutoResetEvent(false);
_positionEventHandle = _positionEvent.SafeWaitHandle;
_terminated = new ManualResetEvent(true);
}
private void EnsureIdle()
{
if (IsCapturing)
throw new SoundCaptureException("Capture is in process");
}
/// <summary>
/// Starts capture process.
/// </summary>
public void Start()
{
EnsureIdle();
IsCapturing = true;
WaveFormat format = new WaveFormat
{
Channels = ChannelCount,
BitsPerSample = BitsPerSample,
SamplesPerSecond = SampleRate,
FormatTag = WaveFormatTag.Pcm,
};
format.BlockAlign = (short)((format.Channels * format.BitsPerSample + 7) / 8);
format.AverageBytesPerSecond = format.BlockAlign * format.SamplesPerSecond;
_bufferLength = format.AverageBytesPerSecond * BufferSeconds;
CaptureBufferDescription desciption = new CaptureBufferDescription
{
Format = format,
BufferBytes = _bufferLength
};
_capture = new Capture(_device.Id);
_buffer = new CaptureBuffer(desciption, _capture);
const int waitHandleCount = BufferSeconds * NotifyPointsInSecond;
BufferPositionNotify[] positions = new BufferPositionNotify[waitHandleCount];
for (int i = 0; i < waitHandleCount; i++)
{
BufferPositionNotify position = new BufferPositionNotify
{
Offset = (i + 1)*_bufferLength/positions.Length - 1,
EventNotifyHandle = _positionEventHandle.DangerousGetHandle()
};
positions[i] = position;
}
_notify = new Notify(_buffer);
_notify.SetNotificationPositions(positions);
_terminated.Reset();
_thread = new Thread(ThreadLoop) {Name = "Sound capture"};
_thread.Start();
}
private void ThreadLoop()
{
_buffer.Start(true);
try
{
int nextCapturePosition = 0;
WaitHandle[] handles = new WaitHandle[] { _terminated, _positionEvent };
while (WaitHandle.WaitAny(handles) > 0)
{
int capturePosition, readPosition;
_buffer.GetCurrentPosition(out capturePosition, out readPosition);
int lockSize = readPosition - nextCapturePosition;
if (lockSize < 0) lockSize += _bufferLength;
if((lockSize & 1) != 0) lockSize--;
int itemsCount = lockSize >> 1;
short[] data = (short[])_buffer.Read(nextCapturePosition, typeof(short), LockFlag.None, itemsCount);
ProcessData(data);
nextCapturePosition = (nextCapturePosition + lockSize) % _bufferLength;
}
}
finally
{
_buffer.Stop();
}
}
/// <summary>
/// Processes the captured data.
/// </summary>
/// <param name="data">Captured data</param>
protected abstract void ProcessData(short[] data);
/// <summary>
/// Stops capture process.
/// </summary>
public void Stop()
{
if (IsCapturing)
{
IsCapturing = false;
_terminated.Set();
//_thread.
_thread.Join();
_notify.Dispose();
_buffer.Dispose();
_capture.Dispose();
}
}
void IDisposable.Dispose()
{
Dispose(true);
}
~SoundCaptureBase()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (_disposed) return;
_disposed = true;
GC.SuppressFinalize(this);
if (IsCapturing) Stop();
_positionEventHandle.Dispose();
_positionEvent.Close();
_terminated.Close();
}
}
}
+68
View File
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using Microsoft.DirectX.DirectSound;
namespace SoundCapture
{
/// <summary>
/// Capture device.
/// </summary>
public class SoundCaptureDevice
{
readonly Guid _id;
readonly string _name;
public bool IsDefault
{
get { return _id == Guid.Empty; }
}
/// <summary>
/// Name of the device.
/// </summary>
public string Name
{
get { return _name; }
}
internal Guid Id
{
get { return _id; }
}
internal SoundCaptureDevice(Guid id, string name)
{
_id = id;
_name = name;
}
public static SoundCaptureDevice[] GetDevices()
{
CaptureDevicesCollection captureDevices = new CaptureDevicesCollection();
List<SoundCaptureDevice> devices = new List<SoundCaptureDevice>();
foreach (DeviceInformation captureDevice in captureDevices)
{
devices.Add(new SoundCaptureDevice(captureDevice.DriverGuid, captureDevice.Description));
}
return devices.ToArray();
}
public static SoundCaptureDevice GetDefaultDevice()
{
CaptureDevicesCollection captureDevices = new CaptureDevicesCollection();
SoundCaptureDevice device = null;
foreach (DeviceInformation captureDevice in captureDevices)
{
if(captureDevice.DriverGuid == Guid.Empty)
{
device = new SoundCaptureDevice(captureDevice.DriverGuid, captureDevice.Description);
break;
}
}
if (device == null)
throw new SoundCaptureException("Default capture device is not found");
return device;
}
}
}
+12
View File
@@ -0,0 +1,12 @@
using System;
namespace SoundCapture
{
public class SoundCaptureException : Exception
{
public SoundCaptureException(string message)
: base(message)
{
}
}
}