diff --git a/PitchTracker/Helper.h b/PitchTracker/Helper.h index f0416375..f9700a6f 100644 --- a/PitchTracker/Helper.h +++ b/PitchTracker/Helper.h @@ -1,9 +1,6 @@ #pragma once #include "pitch.hh" #include "PitchWrapper.h" - -#pragma managed(push, off) - #include #include @@ -43,19 +40,23 @@ void freeDoubleArray(double* floats){ free(floats); } -#pragma managed(pop) +double ToneToNote(const Tone* tone){ + return 12.0 * std::log(tone->freq / 127.09) / std::log(2.0) + 27.5 - 16; // 16 = C2 +} +/* namespace Native{ namespace PitchTracking{ - CTone^ ConvertToneToManaged(Tone tone){ + CTone^ ConvertToneToManaged(Tone* tone){ CTone^ result = gcnew CTone(); - result->Freq = tone.freq; - result->DB = tone.db; - result->Stabledb = tone.stabledb; - result->Age = static_cast(tone.age); - result->NoteExact = 12.0 * std::log(tone.freq / 127.09) / std::log(2.0) + 27.5 - 16; // 16 = C2 + result->Freq = tone->freq; + result->DB = tone->db; + result->Stabledb = tone->stabledb; + result->Age = static_cast(tone->age); + result->NoteExact = 12.0 * std::log(tone->freq / 127.09) / std::log(2.0) + 27.5 - 16; // 16 = C2 result->Note = static_cast(std::round(result->NoteExact)); return result; } } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/PitchTracker/PitchTracker.vcxproj b/PitchTracker/PitchTracker.vcxproj index e17ebdcd..80dcbccb 100644 --- a/PitchTracker/PitchTracker.vcxproj +++ b/PitchTracker/PitchTracker.vcxproj @@ -170,20 +170,7 @@ - - true - true - true - true - false - false - false - false - false - false - Default - Default - + diff --git a/PitchTracker/PitchWrapper.cpp b/PitchTracker/PitchWrapper.cpp index 5677550e..44ffeff1 100644 --- a/PitchTracker/PitchWrapper.cpp +++ b/PitchTracker/PitchWrapper.cpp @@ -1,10 +1,82 @@ #include "PitchWrapper.h" -#pragma managed(push, off) #include "pitchTracker.h" -#pragma managed(pop) - #include "Helper.h" +void PtFast_Init(double baseToneFrequency, int minHalfTone, int maxHalfTone){ + PitchTracker::Init(baseToneFrequency, minHalfTone, maxHalfTone); +} + +void PtFast_DeInit(){ + PitchTracker::DeInit(); +} + +int PtFast_GetTone(short* samples, int sampleCt, float* weights){ + if(sampleCt <= 0) + return -1; + double* dataDouble = short2DoubleArray(samples, sampleCt, false); + int result = PitchTracker::GetTone(dataDouble, sampleCt, weights, true); + freeDoubleArray(dataDouble); + return result; +} + +Analyzer* Analyzer_Create(double rate, unsigned step){ + return new Analyzer(rate, "", step); +} + +void Analyzer_Free(Analyzer* analyzer){ + if(analyzer) + delete analyzer; +} + +void Analyzer_InputFloat(Analyzer* analyzer, float* data, int sampleCt){ + if(sampleCt == 0 || !analyzer) + return; + analyzer->input(data, data + sampleCt); +} + +void Analyzer_InputShort(Analyzer* analyzer, short* data, int sampleCt){ + if(sampleCt == 0 || !analyzer) + return; + float* dataFloat = short2FloatArray(data, sampleCt); + analyzer->input(dataFloat, dataFloat + sampleCt); + freeFloatArray(dataFloat); +} + +void Analyzer_InputByte(Analyzer* analyzer, char* data, int sampleCt){ + if(sampleCt == 0 || !analyzer) + return; + float* dataFloat = short2FloatArray(static_cast(static_cast(data)), sampleCt); + analyzer->input(dataFloat, dataFloat + sampleCt); + freeFloatArray(dataFloat); +} + +void Analyzer_Process(Analyzer* analyzer){ + if(!analyzer) + return; + analyzer->process(); +} + +double Analyzer_GetPeak(Analyzer* analyzer){ + if(!analyzer) + return -999; + return analyzer->getPeak(); +} + +double Analyzer_FindNote(Analyzer* analyzer, double minFreq, double maxFreq){ + if(!analyzer) + return -1; + const Tone* tone = analyzer->findTone(minFreq, maxFreq); + return (tone == NULL) ? -1 : ToneToNote(tone); +} + +bool Analyzer_OutputFloat(Analyzer* analyzer, float* data, int sampleCt, float rate){ + if(!analyzer) + return false; + return analyzer->output(data, data + sampleCt, rate); +} + + +/* namespace Native{ namespace PitchTracking{ @@ -31,7 +103,7 @@ namespace Native{ if(data->Length == 0) return; pin_ptr dataPtr = &data[0]; - float* dataFloat = short2FloatArray(static_cast(static_cast(dataPtr)), data->Length); + float* dataFloat = short2FloatArray(static_cast(static_cast(dataPtr)), data->Length / 2); m_analyzer->input(dataFloat, dataFloat + data->Length); freeFloatArray(dataFloat); } @@ -64,7 +136,7 @@ namespace Native{ CTone^ CAnalyzer::FindTone(double minFreq, double maxFreq){ const Tone* tone = m_analyzer->findTone(minFreq, maxFreq); - return (tone == NULL) ? nullptr : ConvertToneToManaged(*tone); + return (tone == NULL) ? nullptr : ConvertToneToManaged(tone); } void CAnalyzer::Output(array^ data, float rate){ @@ -98,4 +170,5 @@ namespace Native{ return result; } } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/PitchTracker/PitchWrapper.h b/PitchTracker/PitchWrapper.h index 603ec470..2b9783d5 100644 --- a/PitchTracker/PitchWrapper.h +++ b/PitchTracker/PitchWrapper.h @@ -1,63 +1,72 @@ #pragma once -#pragma managed(push, off) #include "pitch.hh" -#pragma managed(pop) -using namespace System; -using namespace System::Collections::Generic; +#define DllExport extern "C" __declspec(dllexport) -namespace Native{ - namespace PitchTracking{ - public ref class CTone { - public: - static const int MAXHARM = 48; ///< The maximum number of harmonics tracked - static const int MINAGE = 2; ///< The minimum age required for a tone to be output - double Freq; ///< Frequency (Hz) - double DB; ///< Level (dB) - double Stabledb; ///< Stable level, useful for graphics rendering - int Age; ///< How many times the tone has been detected in row - double NoteExact; - int Note; - /// Less-than compare by levels (instead of frequencies like operator< does) - static bool dbCompare(CTone l, CTone r) { return l.DB < r.DB; } - }; +DllExport void PtFast_Init(double baseToneFrequency, int minHalfTone, int maxHalfTone); +DllExport void PtFast_DeInit(); +DllExport int PtFast_GetTone(short* samples, int sampleCt, float* weights); - public ref class CAnalyzer { - public: - CAnalyzer(double rate, String^ id) { - m_analyzer = new Analyzer(rate,""); - m_id = id; - } - CAnalyzer(double rate, String^ id, unsigned step) { - m_analyzer = new Analyzer(rate,"",step); - m_id = id; - } - ~CAnalyzer() { this->!CAnalyzer(); } - !CAnalyzer() { delete m_analyzer; } +DllExport Analyzer* Analyzer_Create(double rate, unsigned step); +DllExport void Analyzer_Free(Analyzer* analyzer); +DllExport void Analyzer_InputFloat(Analyzer* analyzer, float* data, int sampleCt); +DllExport void Analyzer_InputShort(Analyzer* analyzer, short* data, int sampleCt); +DllExport void Analyzer_InputByte(Analyzer* analyzer, char* data, int sampleCt); +DllExport void Analyzer_Process(Analyzer* analyzer); +DllExport double Analyzer_GetPeak(Analyzer* analyzer); +DllExport double Analyzer_FindNote(Analyzer* analyzer, double minFreq, double maxFreq); +DllExport bool Analyzer_OutputFloat(Analyzer* analyzer, float* data, int sampleCt, float rate); - void Input(array^ data); - void Input(array^ data); - void Input(array^ data); - void Process(); - double GetPeak(); - array^ GetTones(); - CTone^ FindTone(); - CTone^ FindTone(double minFreq); - CTone^ FindTone(double minFreq, double maxFreq); - void Output(array^ data, float rate); - String^ GetId(); +/* +public ref class CTone { +public: + static const int MAXHARM = 48; ///< The maximum number of harmonics tracked + static const int MINAGE = 2; ///< The minimum age required for a tone to be output + double Freq; ///< Frequency (Hz) + double DB; ///< Level (dB) + double Stabledb; ///< Stable level, useful for graphics rendering + int Age; ///< How many times the tone has been detected in row + double NoteExact; + int Note; + /// Less-than compare by levels (instead of frequencies like operator< does) + static bool dbCompare(CTone l, CTone r) { return l.DB < r.DB; } +}; - private: - Analyzer* m_analyzer; - String^ m_id; - }; - - public ref class CPitchTracker abstract sealed{ - public: - static void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone); - static void DeInit(); - static int GetTone(array^ samples, array^ weights); - }; +public ref class CAnalyzer { +public: + CAnalyzer(double rate, String^ id) { + m_analyzer = new Analyzer(rate,""); + m_id = id; } -} \ No newline at end of file + CAnalyzer(double rate, String^ id, unsigned step) { + m_analyzer = new Analyzer(rate,"",step); + m_id = id; + } + ~CAnalyzer() { this->!CAnalyzer(); } + !CAnalyzer() { delete m_analyzer; } + + void Input(array^ data); + void Input(array^ data); + void Input(array^ data); + void Process(); + double GetPeak(); + array^ GetTones(); + CTone^ FindTone(); + CTone^ FindTone(double minFreq); + CTone^ FindTone(double minFreq, double maxFreq); + void Output(array^ data, float rate); + String^ GetId(); + +private: + Analyzer* m_analyzer; + String^ m_id; +}; + +public ref class CPitchTracker abstract sealed{ +public: + static void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone); + static void DeInit(); + static int GetTone(array^ samples, array^ weights); +}; +*/ \ No newline at end of file diff --git a/PitchTracker/pitch.cc b/PitchTracker/pitch.cc index d4b9d9a0..85169769 100644 --- a/PitchTracker/pitch.cc +++ b/PitchTracker/pitch.cc @@ -48,14 +48,15 @@ Analyzer::Analyzer(double rate, std::string id, std::size_t step): } } -void Analyzer::output(float* begin, float* end, double rate) { +bool Analyzer::output(float* begin, float* end, double rate) { constexpr unsigned a = 2; const unsigned size = m_passthrough.size(); const unsigned out = static_cast((end - begin) / 2 /* stereo */); - if (out == 0) return; + if (out == 0) return false; const unsigned in = static_cast(m_resampleFactor * (m_rate / rate) * out + 2 * a /* lanczos kernel */ + 5 /* safety margin for rounding errors */); float pcm[m_passthrough.capacity]; - m_passthrough.read(pcm, pcm + in + 4); + if(!m_passthrough.read(pcm, pcm + in + 4)) + return false; for (unsigned i = 0; i < out; ++i) { double s = 0.0; unsigned k = static_cast(m_resamplePos); @@ -77,6 +78,7 @@ void Analyzer::output(float* begin, float* end, double rate) { m_passthrough.pop(num); m_resampleFactor = 0.99 * m_resampleFactor + 0.01 * (size > 700 ? 1.02 : 0.98); } + return true; } diff --git a/PitchTracker/pitch.hh b/PitchTracker/pitch.hh index bb543be6..ec759d1f 100644 --- a/PitchTracker/pitch.hh +++ b/PitchTracker/pitch.hh @@ -108,7 +108,7 @@ public: return best; } /** Give data away for mic pass-through */ - void output(float* begin, float* end, double rate); + bool output(float* begin, float* end, double rate); /** Returns the id (color name) of the mic */ std::string const& getId() const { return m_id; } diff --git a/Vocaluxe/Lib/Sound/Record/CAnalyzer.cs b/Vocaluxe/Lib/Sound/Record/CAnalyzer.cs new file mode 100644 index 00000000..14ec16e8 --- /dev/null +++ b/Vocaluxe/Lib/Sound/Record/CAnalyzer.cs @@ -0,0 +1,117 @@ +using System; +using System.Runtime.InteropServices; + +namespace Vocaluxe.Lib.Sound.Record +{ + class CAnalyzer : IDisposable + { + #region Imports + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr Analyzer_Create(double rate, uint step); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern void Analyzer_Free(IntPtr analyzer); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern void Analyzer_InputFloat(IntPtr analyzer, float[] data, int sampleCt); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern void Analyzer_InputShort(IntPtr analyzer, short[] data, int sampleCt); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern void Analyzer_InputByte(IntPtr analyzer, byte[] data, int sampleCt); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern void Analyzer_Process(IntPtr analyzer); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern double Analyzer_GetPeak(IntPtr analyzer); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern double Analyzer_FindNote(IntPtr analyzer, double minFreq, double maxFreq); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern bool Analyzer_OutputFloat(IntPtr analyzer, [Out] float[] data, int sampleCt, float rate); + #endregion + + private IntPtr _Instance; + + public CAnalyzer(double rate = 44100.0, uint step = 200) + { + _Instance = Analyzer_Create(rate, step); + } + + ~CAnalyzer() + { + _Dispose(false); + } + + public void Input(float[] data) + { + Analyzer_InputFloat(_Instance, data, data.Length); + } + + public void Input(short[] data) + { + Analyzer_InputShort(_Instance, data, data.Length); + } + + public void Input(byte[] data) + { + Analyzer_InputByte(_Instance, data, data.Length / 2); + } + + public void Process() + { + Analyzer_Process(_Instance); + } + + public double GetPeak() + { + return Analyzer_GetPeak(_Instance); + } + + public double FindNote(double minFreq = 35.0, double maxFreq = 1000.0) + { + return Analyzer_FindNote(_Instance, minFreq, maxFreq); + } + + public bool Output(float[] data, float rate) + { + return Analyzer_OutputFloat(_Instance, data, data.Length, rate); + } + + private void _Dispose(bool disposing) + { + if (_Instance == IntPtr.Zero) + throw new ObjectDisposedException(GetType().Name); + Analyzer_Free(_Instance); + _Instance = IntPtr.Zero; + } + + public void Dispose() + { + _Dispose(true); + GC.SuppressFinalize(this); + } + } + + static class CFastAnalyzer + { + #region Imports + [DllImport("PitchTracker.dll", EntryPoint = "PtFast_Init", CallingConvention = CallingConvention.Cdecl)] + public static extern void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone); + + [DllImport("PitchTracker.dll", EntryPoint = "PtFast_DeInit", CallingConvention = CallingConvention.Cdecl)] + public static extern void DeInit(); + + [DllImport("PitchTracker.dll", EntryPoint = "PtFast_GetTone", CallingConvention = CallingConvention.Cdecl)] + private static extern int PtFast_GetTone(short[] samples, int sampleCt, float[] weights); + #endregion + + public static int GetTone(short[] samples, float[] weights) + { + return PtFast_GetTone(samples, samples.Length, weights); + } + } +} \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Record/CBuffer.cs b/Vocaluxe/Lib/Sound/Record/CBuffer.cs index ef905ced..408ff5d8 100644 --- a/Vocaluxe/Lib/Sound/Record/CBuffer.cs +++ b/Vocaluxe/Lib/Sound/Record/CBuffer.cs @@ -22,10 +22,7 @@ using System; using System.IO; using System.Diagnostics; -#if USE_NATIVE_DETECTION -using Native.PitchTracking; -#endif #if TEST_PITCH using System.Windows.Forms; using VocaluxeLib; @@ -74,7 +71,7 @@ namespace Vocaluxe.Lib.Sound.Record private double _MinWeightDiff; // ReSharper restore FieldCanBeMadeReadOnly.Local #else - private readonly CAnalyzer _Analyzer = new CAnalyzer(44100.0, ""); + private readonly CAnalyzer _Analyzer = new CAnalyzer(); #endif private double _MaxVolume; private bool _NewSamples; @@ -115,7 +112,7 @@ namespace Vocaluxe.Lib.Sound.Record if (_InitCount > 1) return; //Do init only once #if USE_NATIVE_DETECTION - CPitchTracker.Init(_BaseToneFreq, _HalfToneMin, _HalfToneMax); + CFastAnalyzer.Init(_BaseToneFreq, _HalfToneMin, _HalfToneMax); #else //Init Array to avoid costly calculations for (int toneIndex = 0; toneIndex <= _HalfToneMax; toneIndex++) @@ -239,7 +236,7 @@ namespace Vocaluxe.Lib.Sound.Record private void _AnalyzeTones() { #if USE_NATIVE_DETECTION - int maxTone = CPitchTracker.GetTone(_AnalysisBuffer, _TmpWeights); + int maxTone = CFastAnalyzer.GetTone(_AnalysisBuffer, _TmpWeights); if (maxTone >= 0) #else // prepare to analyze @@ -350,7 +347,7 @@ namespace Vocaluxe.Lib.Sound.Record Debug.Assert(_InitCount > 0); _InitCount--; if (_InitCount == 0) - CPitchTracker.DeInit(); + CFastAnalyzer.DeInit(); #endif } diff --git a/Vocaluxe/Vocaluxe.csproj b/Vocaluxe/Vocaluxe.csproj index fd3f9ab0..d0c8f9ba 100644 --- a/Vocaluxe/Vocaluxe.csproj +++ b/Vocaluxe/Vocaluxe.csproj @@ -199,10 +199,6 @@ ..\Output\OpenTK.GLControl.dll - - ..\Output\x64\PitchTracker.dll - False - False ..\Output\PortAudioSharp.dll @@ -227,6 +223,7 @@ +