From 8eb42b5a5f274c1c7fdc3ea5ecfbec031da1cd08 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Sun, 23 Mar 2014 21:41:40 +0100 Subject: [PATCH] Add a native version of the pitch detection algo as C# is to slow for exact real time detection(Speedup: 10) --- .gitignore | 13 +- PitchTracker/PitchTracker.vcxproj | 178 ++++++++++++++++++++++ PitchTracker/PitchTracker.vcxproj.filters | 27 ++++ PitchTracker/pitchTracker.cpp | 115 ++++++++++++++ PitchTracker/pitchTracker.h | 17 +++ Vocaluxe.sln | 23 +++ Vocaluxe/Lib/Sound/Record/CBuffer.cs | 115 +++++++++++--- 7 files changed, 460 insertions(+), 28 deletions(-) create mode 100644 PitchTracker/PitchTracker.vcxproj create mode 100644 PitchTracker/PitchTracker.vcxproj.filters create mode 100644 PitchTracker/pitchTracker.cpp create mode 100644 PitchTracker/pitchTracker.h diff --git a/.gitignore b/.gitignore index 6413e521..a12662d6 100644 --- a/.gitignore +++ b/.gitignore @@ -14,13 +14,13 @@ Output/Songs /*.ctl /Output/Registration.xml /Vocaluxe.suo -/Vocaluxe/Vocaluxe.csproj.user +*.csproj.user +*.vcxproj.user /Output/Config.xml *.ipch *.log /Output/Playlists/*.xml -/Output/x64/gstreamer/GstreamerHelper.dll -/Output/x86/gstreamer/GstreamerHelper.dll +GstreamerHelper.dll *.tlog *.suo *.obj @@ -39,11 +39,7 @@ Output/Songs *.Cache /GstreamerHelper/bin/* /VocaluxeLib/bin -/ClientServerLib/ClientServerLib/ClientServerLib.csproj.user -/ClientServerLib/ClientServerLib/bin/Debug/ClientServerLib.dll -/ClientServerLib/ClientTest/bin/Debug/ClientServerLib.dll -/ClientServerLib/ClientTest/bin/Release/ClientServerLib.dll -/ClientServerLib/ClientServerLib/bin/ClientServerLib.dll +ClientServerLib.dll ClientTest.exe *.vshost.* *.userprefs @@ -63,3 +59,4 @@ Output/Vocaluxe_x64.exe /Output/VocaluxeServerConfig.exe.config /PhoneGap/ /Output/Profiles +PitchTracker.dll diff --git a/PitchTracker/PitchTracker.vcxproj b/PitchTracker/PitchTracker.vcxproj new file mode 100644 index 00000000..73e5ca52 --- /dev/null +++ b/PitchTracker/PitchTracker.vcxproj @@ -0,0 +1,178 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {54A48AF7-515C-4EDD-B395-2BFFA096050A} + Win32Proj + PitchTracker + PitchTracker + + + + DynamicLibrary + true + v110_xp + Unicode + + + DynamicLibrary + true + v110_xp + Unicode + + + DynamicLibrary + false + v110_xp + true + Unicode + + + DynamicLibrary + false + v110_xp + true + Unicode + + + + + + + + + + + + + + + + + + + true + bin\$(Configuration).$(Platform)\ + obj\$(Configuration).$(Platform)\ + + + true + bin\$(Configuration).$(Platform)\ + obj\$(Configuration).$(Platform)\ + + + false + bin\$(Configuration).$(Platform)\ + obj\$(Configuration).$(Platform)\ + + + false + bin\$(Configuration).$(Platform)\ + obj\$(Configuration).$(Platform)\ + + + + NotUsing + Level3 + Disabled + WIN64;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Fast + + + Windows + true + + + xcopy "$(OutDir)*.dll" "$(SolutionDir)Output\x64\" /I /Y + + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Fast + + + Windows + true + + + xcopy "$(OutDir)*.dll" "$(SolutionDir)Output\x86\" /I /Y + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN64;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Speed + true + Fast + + + Windows + false + true + true + + + xcopy "$(OutDir)*.dll" "$(SolutionDir)Output\x64\" /I /Y + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Speed + Fast + + + Windows + false + true + true + + + xcopy "$(OutDir)*.dll" "$(SolutionDir)Output\x86\" /I /Y + + + + + + + + + + + + \ No newline at end of file diff --git a/PitchTracker/PitchTracker.vcxproj.filters b/PitchTracker/PitchTracker.vcxproj.filters new file mode 100644 index 00000000..aa287032 --- /dev/null +++ b/PitchTracker/PitchTracker.vcxproj.filters @@ -0,0 +1,27 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Quelldateien + + + + + Headerdateien + + + \ No newline at end of file diff --git a/PitchTracker/pitchTracker.cpp b/PitchTracker/pitchTracker.cpp new file mode 100644 index 00000000..72904102 --- /dev/null +++ b/PitchTracker/pitchTracker.cpp @@ -0,0 +1,115 @@ +// This file is part of Vocaluxe. +// +// Vocaluxe is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vocaluxe is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vocaluxe. If not, see . + +#include "pitchTracker.h" +#include +#include +#include +#include + +void doLog(const char* msg,...){ + va_list argptr = NULL; + va_start(argptr, msg); + + char buffer[200]; + vsprintf_s(buffer,msg, argptr); + if(Log) Log(buffer); + va_end(argptr); +} + +void SetLogCallback(LogCallback Callback){ + Log=Callback; +} + +double *SamplesPerPeriodPerTone = NULL; +int MinHalfTone = 0, MaxHalfTone = 38; +const double HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave) + +void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone){ + MinHalfTone = minHalfTone; + MaxHalfTone = maxHalfTone; + DeInit(); + if(maxHalfTone <= 0) + return; + SamplesPerPeriodPerTone = (double*) malloc(MaxHalfTone * sizeof(double)); + //Init Array to avoid costly calculations + for (int toneIndex = 0; toneIndex <= MaxHalfTone; toneIndex++) + { + double freq = baseToneFrequency * pow(HalftoneBase, toneIndex); + SamplesPerPeriodPerTone[toneIndex] = 44100.0 / freq; // samples in one period + } +} + +void DeInit(){ + if(SamplesPerPeriodPerTone){ + free(SamplesPerPeriodPerTone); + SamplesPerPeriodPerTone=NULL; + } +} + +static double _AnalyzeToneFunc(double *samples, int sampleCt, int toneIndex){ + double samplesPerPeriodD = SamplesPerPeriodPerTone[toneIndex]; // samples in one period + int samplesPerPeriod = (int)samplesPerPeriodD; + double fHigh = samplesPerPeriodD - samplesPerPeriod; + double fLow = 1.0 - fHigh; + + double accumDist = 0; // accumulated distances + + // compare correlating samples + int sampleIndex = 0; // index of sample to analyze + // Start value= index of sample one period ahead + for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex + 1 < sampleCt; correlatingSampleIndex++, sampleIndex++) + { + // calc distance to corresponding sample in next period (lower means more distant) + double dist = samples[sampleIndex] * (samples[correlatingSampleIndex] * fLow + samples[correlatingSampleIndex + 1] * fHigh); + accumDist += dist; + } + + //Using _AnalysisBufLen here makes it return correct values among all analyzed frequencies + double scaleValue = 32767.0 * 32767.0 * sampleCt; // Divide also by Int16.MaxValue^2 + return accumDist / scaleValue; +} + +int GetTone(double *samples, int sampleCt, float *weights){ + double maxVolume = 0; + for(int i = 0; i < sampleCt; i++){ + double vol = abs(samples[i]); + if(vol > maxVolume) + maxVolume = vol; + } + double maxWeight = -1; + double minWeight = 1; + int maxTone = -1; + for (int toneIndex = MinHalfTone; toneIndex <= MaxHalfTone; toneIndex++) + { + double curWeight = _AnalyzeToneFunc(samples, sampleCt, toneIndex) / maxVolume * 32767.0; + + if (curWeight > maxWeight) + { + maxWeight = curWeight; + maxTone = toneIndex; + } + + if (curWeight < minWeight) + minWeight = curWeight; + + weights[toneIndex] = (float) curWeight; + } + + if(maxWeight - minWeight > 0.01){ + return maxTone; + }else return -1; +} + diff --git a/PitchTracker/pitchTracker.h b/PitchTracker/pitchTracker.h new file mode 100644 index 00000000..99b477b0 --- /dev/null +++ b/PitchTracker/pitchTracker.h @@ -0,0 +1,17 @@ +#pragma once + +#ifndef dywapitchtrack__H +#define dywapitchtrack__H + +#define DllExport extern "C" __declspec(dllexport) + +DllExport void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone); +DllExport void DeInit(); +DllExport int GetTone(double *samples, int sampleCt, float *weights); + +typedef void (__stdcall * LogCallback)(const char* text); +static LogCallback Log; +DllExport void SetLogCallback(LogCallback Callback); + +#endif + diff --git a/Vocaluxe.sln b/Vocaluxe.sln index 347f685a..0298c529 100644 --- a/Vocaluxe.sln +++ b/Vocaluxe.sln @@ -9,6 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vocaluxe", "Vocaluxe\Vocalu {EC5167E4-CCFB-4CEE-987C-CF816F1062A3} = {EC5167E4-CCFB-4CEE-987C-CF816F1062A3} {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC} = {D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC} {5A4053F1-5AD5-441F-B254-D97A088127B6} = {5A4053F1-5AD5-441F-B254-D97A088127B6} + {54A48AF7-515C-4EDD-B395-2BFFA096050A} = {54A48AF7-515C-4EDD-B395-2BFFA096050A} EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VocaluxeLib", "VocaluxeLib\VocaluxeLib.csproj", "{D2B6DDEF-E0E1-4DDF-A73A-E6ACEF5C95EC}" @@ -25,6 +26,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerLib", "ClientServerLi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebserverInitalConfig", "WebserverInitalConfig\WebserverInitalConfig.csproj", "{55F89808-4E79-4FFE-BC13-48258913F5C9}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PitchTracker", "PitchTracker\PitchTracker.vcxproj", "{54A48AF7-515C-4EDD-B395-2BFFA096050A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution DebugLinux|x64 = DebugLinux|x64 @@ -191,6 +194,26 @@ Global {55F89808-4E79-4FFE-BC13-48258913F5C9}.ReleaseWin|x64.Build.0 = Release|Any CPU {55F89808-4E79-4FFE-BC13-48258913F5C9}.ReleaseWin|x86.ActiveCfg = Release|Any CPU {55F89808-4E79-4FFE-BC13-48258913F5C9}.ReleaseWin|x86.Build.0 = Release|Any CPU + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugLinux|x64.ActiveCfg = Debug|x64 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugLinux|x64.Build.0 = Debug|x64 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugLinux|x86.ActiveCfg = Debug|Win32 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugLinux|x86.Build.0 = Debug|Win32 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugWin|x64.ActiveCfg = Debug|x64 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugWin|x64.Build.0 = Debug|x64 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugWin|x86.ActiveCfg = Debug|Win32 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.DebugWin|x86.Build.0 = Debug|Win32 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.InstallerWin|x64.ActiveCfg = Release|x64 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.InstallerWin|x64.Build.0 = Release|x64 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.InstallerWin|x86.ActiveCfg = Release|Win32 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.InstallerWin|x86.Build.0 = Release|Win32 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseLinux|x64.ActiveCfg = Release|x64 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseLinux|x64.Build.0 = Release|x64 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseLinux|x86.ActiveCfg = Release|Win32 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseLinux|x86.Build.0 = Release|Win32 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseWin|x64.ActiveCfg = Release|x64 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseWin|x64.Build.0 = Release|x64 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseWin|x86.ActiveCfg = Release|Win32 + {54A48AF7-515C-4EDD-B395-2BFFA096050A}.ReleaseWin|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Vocaluxe/Lib/Sound/Record/CBuffer.cs b/Vocaluxe/Lib/Sound/Record/CBuffer.cs index 7aece259..70334af4 100644 --- a/Vocaluxe/Lib/Sound/Record/CBuffer.cs +++ b/Vocaluxe/Lib/Sound/Record/CBuffer.cs @@ -1,9 +1,29 @@ -#define TEST_PITCH +#region license +// This file is part of Vocaluxe. +// +// Vocaluxe is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Vocaluxe is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Vocaluxe. If not, see . +#endregion + +#define TEST_PITCH +#define USE_NATIVE_DETECTION +//If defined, it uses a library with native code that speeds up detection by a factor of 10 using System; using System.IO; #if TEST_PITCH using System.Diagnostics; +using System.Runtime.InteropServices; using System.Windows.Forms; using VocaluxeLib; @@ -11,16 +31,29 @@ using VocaluxeLib; namespace Vocaluxe.Lib.Sound.Record { +#if USE_NATIVE_DETECTION + static class CPitchTracker + { + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void DeInit(); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern int GetTone(double[] samples, int sampleCt, float[] weights); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate void LogCallback(string message); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void SetLogCallback(LogCallback c); + } +#endif + class CBuffer : IDisposable { - public enum EAnalyzeFunction - { - AutoCorrelation, //Faster, more resistent to noise, good value for HalfToneMax=38 (higher->wrong results in test case, WHY?) with 38 no wrong values in test case - Amdf //Average magnitude difference, slower, detects everything in range -> HalfToneMax=45 - } - - private delegate double DAnalyzeTone(int tone); - + private static int _InitCount; //Half tones: C C♯ D Eb E F F♯ G G♯ A Bb B private const double _BaseToneFreq = 65.4064; // lowest (half-)tone to analyze (C2 = 65.4064 Hz) private const double _HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave) @@ -34,9 +67,18 @@ namespace Vocaluxe.Lib.Sound.Record private readonly double[] _AnalysisBuffer = new double[_AnalysisBufLen]; private readonly byte[] _AnalysisByteBuffer = new byte[_AnalysisByteBufLen]; //tmp buffer (stream->tmpBuffer->(Int16)AnalysisBuffer) - //Precalculated table holding sample# per period for each tone (actually we may not use the lower few entries, but keep it to avoid index modification + private readonly float[] _TmpWeights = new float[NumHalfTones]; //tmp buffer for weights (gets copied to ToneWeight when checked) +#if !USE_NATIVE_DETECTION + //Precalculated table holding sample# per period for each tone (actually we may not use the lower few entries, but keep it to avoid index modification private static readonly double[] _SamplesPerPeriodPerTone = new double[_HalfToneMax + 1]; - private readonly float[] _TmpWeight = new float[NumHalfTones]; //tmp buffer for weights (gets copied to ToneWeight when checked) + + public enum EAnalyzeFunction + { + AutoCorrelation, //Faster, more resistent to noise, good value for HalfToneMax=38 (higher->wrong results in test case, WHY?) with 38 no wrong values in test case + Amdf //Average magnitude difference, slower, detects everything in range -> HalfToneMax=45 + } + private delegate double DAnalyzeTone(int tone); + // ReSharper disable MemberCanBePrivate.Global // ReSharper disable ConvertToConstant.Global // ReSharper disable FieldCanBeMadeReadOnly.Global @@ -48,7 +90,7 @@ namespace Vocaluxe.Lib.Sound.Record private DAnalyzeTone _AnalyzeToneFunc; private double _MinWeightDiff; // ReSharper restore FieldCanBeMadeReadOnly.Local - +#endif private double _MaxVolume; private bool _NewSamples; @@ -56,9 +98,11 @@ namespace Vocaluxe.Lib.Sound.Record public CBuffer() { + _Init(); MinVolume = 0.02f; ToneWeigth = new float[NumHalfTones]; Reset(); +#if !USE_NATIVE_DETECTION if (AnalyzeFunction == EAnalyzeFunction.Amdf) { _AnalyzeToneFunc = _GetAmdf; @@ -69,19 +113,32 @@ namespace Vocaluxe.Lib.Sound.Record _AnalyzeToneFunc = _GetAutoCorrelation; _MinWeightDiff = 0.0001; } +#endif #if TEST_PITCH _TestPitchDetection(); #endif } - static CBuffer() + ~CBuffer() { - //Init Array to avoid costly calculations + _Dispose(); + } + + private static void _Init() + { + _InitCount++; + if (_InitCount > 1) + return; //Do init only once +#if USE_NATIVE_DETECTION + CPitchTracker.Init(_BaseToneFreq, _HalfToneMin, _HalfToneMax); +#else + //Init Array to avoid costly calculations for (int toneIndex = 0; toneIndex <= _HalfToneMax; toneIndex++) { double freq = _BaseToneFreq * Math.Pow(_HalftoneBase, toneIndex); _SamplesPerPeriodPerTone[toneIndex] = 44100.0 / freq; // samples in one period } +#endif } public int ToneAbs { get; private set; } @@ -98,7 +155,7 @@ namespace Vocaluxe.Lib.Sound.Record public float[] ToneWeigth { get; private set; } /// - /// Minimum volume for a tone to be valid + /// Minimum volume for a tone to be valid /// public float MinVolume { get; set; } @@ -183,7 +240,11 @@ namespace Vocaluxe.Lib.Sound.Record private void _AnalyzeTones() { - // prepare to analyze +#if USE_NATIVE_DETECTION + int maxTone = CPitchTracker.GetTone(_AnalysisBuffer, _AnalysisBufLen, _TmpWeights); + if (maxTone >= 0) +#else + // prepare to analyze double maxWeight = -1.0; double minWeight = 1.0; int maxTone = -1; @@ -206,12 +267,13 @@ namespace Vocaluxe.Lib.Sound.Record if (curWeight < minWeight) minWeight = curWeight; - _TmpWeight[toneIndex - _HalfToneMin] = (float)curWeight; + _TmpWeights[toneIndex - _HalfToneMin] = (float)curWeight; } if (maxWeight - minWeight > _MinWeightDiff) +#endif { - Array.Copy(_TmpWeight, ToneWeigth, NumHalfTones); + Array.Copy(_TmpWeights, ToneWeigth, NumHalfTones); ToneAbs = maxTone; //Console.WriteLine(maxTone); @@ -222,6 +284,7 @@ namespace Vocaluxe.Lib.Sound.Record ToneValid = false; } +#if !USE_NATIVE_DETECTION private double _GetAutoCorrelation(int tone) { double samplesPerPeriodD = _SamplesPerPeriodPerTone[tone]; // samples in one period @@ -269,17 +332,29 @@ namespace Vocaluxe.Lib.Sound.Record accumDist += dist; } //Use analyzed sample count here. BufLen yields more errors - return 1.0 - (double)accumDist / Int16.MaxValue / sampleIndex; + return 1.0 - accumDist / Int16.MaxValue / sampleIndex; } +#endif public void Dispose() + { + _Dispose(); + GC.SuppressFinalize(this); + } + + private void _Dispose() { if (_Stream != null) { _Stream.Dispose(); _Stream = null; } - GC.SuppressFinalize(this); +#if USE_NATIVE_DETECTION + Debug.Assert(_InitCount > 0); + _InitCount--; + if (_InitCount == 0) + CPitchTracker.DeInit(); +#endif } #if TEST_PITCH