diff --git a/PitchTracker/Helper.h b/PitchTracker/Helper.h index f9700a6f..27c18e8b 100644 --- a/PitchTracker/Helper.h +++ b/PitchTracker/Helper.h @@ -9,7 +9,7 @@ float* short2FloatArray(short* in, size_t len){ float* result = static_cast(malloc(len * sizeof(float))); float* curOut = result; - for(int i=0; i(malloc(len * sizeof(double))); double* curOut = result; - for(int i=0; iWIN64;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Speed - true Fast + false Windows diff --git a/PitchTracker/PitchWrapper.cpp b/PitchTracker/PitchWrapper.cpp index 44ffeff1..45a29b18 100644 --- a/PitchTracker/PitchWrapper.cpp +++ b/PitchTracker/PitchWrapper.cpp @@ -2,6 +2,22 @@ #include "pitchTracker.h" #include "Helper.h" +AnalyzerExt::AnalyzerExt(double baseToneFrequency, int minHalfTone, int maxHalfTone, unsigned step): Analyzer(44100., "", step){ + PitchTracker::Init(baseToneFrequency, minHalfTone, maxHalfTone); +} + +AnalyzerExt::~AnalyzerExt(){ + PitchTracker::DeInit(); +} + +int AnalyzerExt::GetNoteFast(float* weights){ + size_t size = m_fastAnalysisBuf.size(); + if(size > m_SampleCt) + m_fastAnalysisBuf.pop(size - m_SampleCt); + m_fastAnalysisBuf.read(m_AnalysisBuf, m_AnalysisBuf + m_SampleCt); + return PitchTracker::GetTone(m_AnalysisBuf, m_SampleCt, weights); +} + void PtFast_Init(double baseToneFrequency, int minHalfTone, int maxHalfTone){ PitchTracker::Init(baseToneFrequency, minHalfTone, maxHalfTone); } @@ -19,22 +35,22 @@ int PtFast_GetTone(short* samples, int sampleCt, float* weights){ return result; } -Analyzer* Analyzer_Create(double rate, unsigned step){ - return new Analyzer(rate, "", step); +AnalyzerExt* Analyzer_Create(double baseToneFrequency, int minHalfTone, int maxHalfTone, unsigned step){ + return new AnalyzerExt(baseToneFrequency, minHalfTone, maxHalfTone, step); } -void Analyzer_Free(Analyzer* analyzer){ +void Analyzer_Free(AnalyzerExt* analyzer){ if(analyzer) delete analyzer; } -void Analyzer_InputFloat(Analyzer* analyzer, float* data, int sampleCt){ +void Analyzer_InputFloat(AnalyzerExt* analyzer, float* data, int sampleCt){ if(sampleCt == 0 || !analyzer) return; analyzer->input(data, data + sampleCt); } -void Analyzer_InputShort(Analyzer* analyzer, short* data, int sampleCt){ +void Analyzer_InputShort(AnalyzerExt* analyzer, short* data, int sampleCt){ if(sampleCt == 0 || !analyzer) return; float* dataFloat = short2FloatArray(data, sampleCt); @@ -42,7 +58,7 @@ void Analyzer_InputShort(Analyzer* analyzer, short* data, int sampleCt){ freeFloatArray(dataFloat); } -void Analyzer_InputByte(Analyzer* analyzer, char* data, int sampleCt){ +void Analyzer_InputByte(AnalyzerExt* analyzer, char* data, int sampleCt){ if(sampleCt == 0 || !analyzer) return; float* dataFloat = short2FloatArray(static_cast(static_cast(data)), sampleCt); @@ -50,26 +66,32 @@ void Analyzer_InputByte(Analyzer* analyzer, char* data, int sampleCt){ freeFloatArray(dataFloat); } -void Analyzer_Process(Analyzer* analyzer){ +void Analyzer_Process(AnalyzerExt* analyzer){ if(!analyzer) return; analyzer->process(); } -double Analyzer_GetPeak(Analyzer* analyzer){ +double Analyzer_GetPeak(AnalyzerExt* analyzer){ if(!analyzer) return -999; return analyzer->getPeak(); } -double Analyzer_FindNote(Analyzer* analyzer, double minFreq, double maxFreq){ +double Analyzer_FindNote(AnalyzerExt* 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){ +int Analyzer_GetNoteFast(AnalyzerExt* analyzer, float* weights){ + if(!analyzer) + return -1; + return analyzer->GetNoteFast(weights); +} + +bool Analyzer_OutputFloat(AnalyzerExt* analyzer, float* data, int sampleCt, float rate){ if(!analyzer) return false; return analyzer->output(data, data + sampleCt, rate); diff --git a/PitchTracker/PitchWrapper.h b/PitchTracker/PitchWrapper.h index 2b9783d5..09298158 100644 --- a/PitchTracker/PitchWrapper.h +++ b/PitchTracker/PitchWrapper.h @@ -4,19 +4,35 @@ #define DllExport extern "C" __declspec(dllexport) +class AnalyzerExt: public Analyzer{ +public: + AnalyzerExt(double baseToneFrequency, int minHalfTone, int maxHalfTone, unsigned step = 200); + ~AnalyzerExt(); + template void input(InIt begin, InIt end){ + m_fastAnalysisBuf.insert(begin, end); + Analyzer::input(begin, end); + } + int GetNoteFast(float* weights); +private: + constexpr static size_t m_SampleCt = 4096; + RingBuffer m_fastAnalysisBuf; // Buffer used for fast analysis instead of FFT + float m_AnalysisBuf[4096]; +}; + DllExport void PtFast_Init(double baseToneFrequency, int minHalfTone, int maxHalfTone); DllExport void PtFast_DeInit(); DllExport int PtFast_GetTone(short* samples, int sampleCt, float* weights); -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); +DllExport AnalyzerExt* Analyzer_Create(double baseToneFrequency, int minHalfTone, int maxHalfTone, unsigned step); +DllExport void Analyzer_Free(AnalyzerExt* analyzer); +DllExport void Analyzer_InputFloat(AnalyzerExt* analyzer, float* data, int sampleCt); +DllExport void Analyzer_InputShort(AnalyzerExt* analyzer, short* data, int sampleCt); +DllExport void Analyzer_InputByte(AnalyzerExt* analyzer, char* data, int sampleCt); +DllExport void Analyzer_Process(AnalyzerExt* analyzer); +DllExport double Analyzer_GetPeak(AnalyzerExt* analyzer); +DllExport int Analyzer_GetNoteFast(AnalyzerExt* analyzer, float* weights); +DllExport double Analyzer_FindNote(AnalyzerExt* analyzer, double minFreq, double maxFreq); +DllExport bool Analyzer_OutputFloat(AnalyzerExt* analyzer, float* data, int sampleCt, float rate); /* public ref class CTone { @@ -59,7 +75,7 @@ public: String^ GetId(); private: - Analyzer* m_analyzer; + AnalyzerExt* m_analyzer; String^ m_id; }; diff --git a/PitchTracker/pitch.cc b/PitchTracker/pitch.cc index 076dbc70..85af7d5f 100644 --- a/PitchTracker/pitch.cc +++ b/PitchTracker/pitch.cc @@ -50,7 +50,7 @@ Analyzer::Analyzer(double rate, std::string id, unsigned step): bool Analyzer::output(float* begin, float* end, double rate) { constexpr unsigned a = 2; - const unsigned size = m_passthrough.size(); + const size_t size = m_passthrough.size(); const unsigned out = static_cast((end - begin) / 2 /* stereo */); 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 */); @@ -72,7 +72,7 @@ bool Analyzer::output(float* begin, float* end, double rate) { m_resamplePos -= num; if (size > 3000) { // Reset - m_passthrough.pop(m_passthrough.size() - 700); + m_passthrough.pop(size - 700); m_resampleFactor = 1.0; } else { m_passthrough.pop(num); @@ -133,8 +133,8 @@ void Analyzer::calcTones() { const size_t kMax = std::min(FFT_N / 2, size_t(FFT_MAXFREQ / freqPerBin)); std::vector peaks(kMax + 1); // One extra to simplify loops for (size_t k = 1; k <= kMax; ++k) { - float magnitude = std::abs(m_fft[k]); - float phase = std::arg(m_fft[k]); + double magnitude = std::abs(m_fft[k]); + double phase = std::arg(m_fft[k]); // process phase difference double delta = phase - m_fftLastPhase[k]; m_fftLastPhase[k] = phase; diff --git a/PitchTracker/pitch.hh b/PitchTracker/pitch.hh index fc431733..f153ecd8 100644 --- a/PitchTracker/pitch.hh +++ b/PitchTracker/pitch.hh @@ -34,14 +34,14 @@ static const unsigned FFT_P = 10; static const std::size_t FFT_N = 1 << FFT_P; /// Lock-free ring buffer. Discards oldest data on overflow (not strictly thread-safe). -template class RingBuffer { +template class RingBuffer { public: - constexpr static unsigned capacity = SIZE; + constexpr static size_t capacity = SIZE; RingBuffer(): m_read(), m_write() {} ///< Initialize empty buffer template void insert(InIt begin, InIt end) { - unsigned r = m_read; // The read position - unsigned w = m_write; // The write position - bool overflow = (end - begin >= SIZE - size()); + size_t r = m_read; // The read position + size_t w = m_write; // The write position + bool overflow = (static_cast(end - begin) >= SIZE - size()); while (begin != end) { m_buf[w] = *begin++; // Copy sample w = modulo(w + 1); // Update cursor @@ -51,17 +51,17 @@ public: } /// Read data from current position if there is enough data to fill the range (otherwise return false). Does not move read pointer. template bool read(OutIt begin, OutIt end) { - unsigned r = m_read; - if (size() <= end - begin) return false; // Not enough audio available + size_t r = m_read; + if (size() <= static_cast(end - begin)) return false; // Not enough audio available while (begin != end) *begin++ = m_buf[r++ % SIZE]; // Copy audio to output iterator return true; } - void pop(unsigned n) { m_read = modulo(m_read + n); } ///< Move reading pointer forward. - unsigned size() const { return modulo(m_write - m_read); } + void pop(size_t n) { m_read = modulo(m_read + n); } ///< Move reading pointer forward. + size_t size() const { return modulo(m_write - m_read); } private: - static unsigned modulo(int idx) { return (SIZE + idx) % SIZE; } ///< Modulo operation with proper rounding (handles slightly "negative" idx as well) + static size_t modulo(size_t idx) { return idx % SIZE; } ///< Modulo operation with proper rounding (handles slightly "negative" idx as well) float m_buf[SIZE]; - volatile unsigned m_read, m_write; ///< The indices of the next read/write operations. read == write implies that buffer is empty. + volatile size_t m_read, m_write; ///< The indices of the next read/write operations. read == write implies that buffer is empty. }; /// analyzer class diff --git a/PitchTracker/pitchTracker.cpp b/PitchTracker/pitchTracker.cpp index 4d472ee2..bd075c7e 100644 --- a/PitchTracker/pitchTracker.cpp +++ b/PitchTracker/pitchTracker.cpp @@ -35,15 +35,19 @@ namespace PitchTracker{ Log=Callback; } - double *SamplesPerPeriodPerTone = NULL; - int MinHalfTone = 0, MaxHalfTone = 38; - const double HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave) + static double *SamplesPerPeriodPerTone = NULL; + static double BaseToneFrequency = -1; + static int MinHalfTone = -1, MaxHalfTone = -1; + static const double HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave) void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone){ + if(SamplesPerPeriodPerTone && abs(baseToneFrequency - BaseToneFrequency) < 1. && minHalfTone == MinHalfTone && maxHalfTone == MaxHalfTone) + return; + BaseToneFrequency = baseToneFrequency; MinHalfTone = minHalfTone; MaxHalfTone = maxHalfTone; DeInit(); - if(maxHalfTone <= 0) + if(minHalfTone < 0 || maxHalfTone < minHalfTone) return; SamplesPerPeriodPerTone = (double*) malloc((MaxHalfTone + 1) * sizeof(double)); //Init Array to avoid costly calculations @@ -61,13 +65,14 @@ namespace PitchTracker{ } } - static double _AnalyzeToneFunc(double *samples, int sampleCt, int toneIndex){ + template + static T _AnalyzeToneFunc(T *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; + int samplesPerPeriod = static_cast(samplesPerPeriodD); + T fHigh = static_cast(samplesPerPeriodD - samplesPerPeriod); + T fLow = static_cast(1.0 - fHigh); - double accumDist = 0; // accumulated distances + T accumDist = 0; // accumulated distances // compare correlating samples int sampleIndex = 0; // index of sample to analyze @@ -75,28 +80,30 @@ namespace PitchTracker{ 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); + T 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 = sampleCt; // Divide also by Int16.MaxValue^2 - return accumDist / scaleValue; + //Using sampleCt here makes it return correct values among all analyzed frequencies + return accumDist / sampleCt; } - int GetTone(double *samples, int sampleCt, float *weights, bool scale){ - double maxVolume = 0; + template + int GetTone(T *samples, int sampleCt, float *weights, bool scale){ + if(!SamplesPerPeriodPerTone) + return -1; + T maxVolume = 0; for(int i = 0; i < sampleCt; i++){ - double vol = abs(samples[i]); + T vol = abs(samples[i]); if(vol > maxVolume) maxVolume = vol; } - double maxWeight = -1; - double minWeight = 1; + T maxWeight = -1; + T minWeight = 1; int maxTone = -1; for (int toneIndex = MinHalfTone; toneIndex <= MaxHalfTone; toneIndex++) { - double curWeight = _AnalyzeToneFunc(samples, sampleCt, toneIndex) / maxVolume; + T curWeight = _AnalyzeToneFunc(samples, sampleCt, toneIndex) / maxVolume; if(scale) curWeight /= 32768.0; //maximum abs value of a short, would actually need to scale by MaxValue^2 but we already scaled by maxVolume @@ -109,7 +116,7 @@ namespace PitchTracker{ if (curWeight < minWeight) minWeight = curWeight; - weights[toneIndex - MinHalfTone] = (float) curWeight; + weights[toneIndex - MinHalfTone] = static_cast(curWeight); } if(maxWeight - minWeight > 0.01){ @@ -117,4 +124,7 @@ namespace PitchTracker{ }else return -1; } + template int GetTone(float *samples, int sampleCt, float *weights, bool scale); + template int GetTone(double *samples, int sampleCt, float *weights, bool scale); + } \ No newline at end of file diff --git a/PitchTracker/pitchTracker.h b/PitchTracker/pitchTracker.h index 961c436c..a03d5926 100644 --- a/PitchTracker/pitchTracker.h +++ b/PitchTracker/pitchTracker.h @@ -3,7 +3,7 @@ namespace PitchTracker{ void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone); void DeInit(); - int GetTone(double *samples, int sampleCt, float *weights, bool scale = false); + template int GetTone(T *samples, int sampleCt, float *weights, bool scale = false); typedef void (__stdcall * LogCallback)(const char* text); static LogCallback Log; diff --git a/Vocaluxe/Lib/Sound/Record/CAnalyzer.cs b/Vocaluxe/Lib/Sound/Record/CAnalyzer.cs index 14ec16e8..676f37b3 100644 --- a/Vocaluxe/Lib/Sound/Record/CAnalyzer.cs +++ b/Vocaluxe/Lib/Sound/Record/CAnalyzer.cs @@ -7,19 +7,19 @@ namespace Vocaluxe.Lib.Sound.Record { #region Imports [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr Analyzer_Create(double rate, uint step); + private static extern IntPtr Analyzer_Create(double baseToneFrequency, int minHalfTone, int maxHalfTone, uint step = 200); [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); + private static extern void Analyzer_InputFloat(IntPtr analyzer, [In] float[] data, int sampleCt); [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] - private static extern void Analyzer_InputShort(IntPtr analyzer, short[] data, int sampleCt); + private static extern void Analyzer_InputShort(IntPtr analyzer, [In] short[] data, int sampleCt); [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] - private static extern void Analyzer_InputByte(IntPtr analyzer, byte[] data, int sampleCt); + private static extern void Analyzer_InputByte(IntPtr analyzer, [In] byte[] data, int sampleCt); [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void Analyzer_Process(IntPtr analyzer); @@ -27,6 +27,9 @@ namespace Vocaluxe.Lib.Sound.Record [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_GetNoteFast(IntPtr analyzer, [Out] float[] weights); + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] private static extern double Analyzer_FindNote(IntPtr analyzer, double minFreq, double maxFreq); @@ -36,9 +39,9 @@ namespace Vocaluxe.Lib.Sound.Record private IntPtr _Instance; - public CAnalyzer(double rate = 44100.0, uint step = 200) + public CAnalyzer(double baseToneFrequency, int minHalfTone, int maxHalfTone, uint step = 200) { - _Instance = Analyzer_Create(rate, step); + _Instance = Analyzer_Create(baseToneFrequency, minHalfTone, maxHalfTone, step); } ~CAnalyzer() @@ -76,6 +79,11 @@ namespace Vocaluxe.Lib.Sound.Record return Analyzer_FindNote(_Instance, minFreq, maxFreq); } + public double GetNoteFast(float[] weights) + { + return Analyzer_GetNoteFast(_Instance, weights); + } + public bool Output(float[] data, float rate) { return Analyzer_OutputFloat(_Instance, data, data.Length, rate); @@ -106,7 +114,7 @@ namespace Vocaluxe.Lib.Sound.Record 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); + private static extern int PtFast_GetTone([In] short[] samples, int sampleCt, [Out] float[] weights); #endregion public static int GetTone(short[] samples, float[] weights) diff --git a/Vocaluxe/Lib/Sound/Record/CBuffer.cs b/Vocaluxe/Lib/Sound/Record/CBuffer.cs index 408ff5d8..edcbb9b3 100644 --- a/Vocaluxe/Lib/Sound/Record/CBuffer.cs +++ b/Vocaluxe/Lib/Sound/Record/CBuffer.cs @@ -15,16 +15,12 @@ // 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 +#define TEST_PITCH using System; -using System.IO; -using System.Diagnostics; - #if TEST_PITCH using System.Windows.Forms; +using System.Diagnostics; using VocaluxeLib; #endif @@ -33,69 +29,23 @@ namespace Vocaluxe.Lib.Sound.Record { class CBuffer : IDisposable { - private static int _InitCount; //Half tones: C C♯ D D# E F F♯ G G♯ A A# B private const double _BaseToneFreq = 65.4064; // lowest (half-)tone to analyze (C2 = 65.4064 Hz) +#if TEST_PITCH private const double _HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave) +#endif private const int _HalfToneMin = 0; // C2 private const int _HalfToneMax = 38; //45; // inclusive, 38=D5; 45=A5 public const int NumHalfTones = _HalfToneMax - _HalfToneMin + 1; //Number of halftones to analyze + private CAnalyzer _Analyzer = new CAnalyzer(_BaseToneFreq, _HalfToneMin, _HalfToneMax); - private const int _AnalysisBufLen = 4096; - private const int _AnalysisByteBufLen = _AnalysisBufLen * 2; - - private readonly short[] _AnalysisBuffer = new short[_AnalysisBufLen]; - private readonly byte[] _AnalysisByteBuffer = new byte[_AnalysisByteBufLen]; //tmp buffer (stream->tmpBuffer->(Int16)AnalysisBuffer) - - 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]; - - 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 - public static EAnalyzeFunction AnalyzeFunction = EAnalyzeFunction.AutoCorrelation; - // ReSharper restore FieldCanBeMadeReadOnly.Global - // ReSharper restore ConvertToConstant.Global - // ReSharper restore MemberCanBePrivate.Global - // ReSharper disable FieldCanBeMadeReadOnly.Local - private DAnalyzeTone _AnalyzeToneFunc; - private double _MinWeightDiff; - // ReSharper restore FieldCanBeMadeReadOnly.Local -#else - private readonly CAnalyzer _Analyzer = new CAnalyzer(); -#endif private double _MaxVolume; - private bool _NewSamples; - - private MemoryStream _Stream = new MemoryStream(); // full buffer public CBuffer() { - _Init(); MinVolume = 0.02f; - ToneWeigth = new float[NumHalfTones]; + ToneWeigths = new float[NumHalfTones]; Reset(); -#if !USE_NATIVE_DETECTION - if (AnalyzeFunction == EAnalyzeFunction.Amdf) - { - _AnalyzeToneFunc = _GetAmdf; - _MinWeightDiff = 0.01; - } - else if (AnalyzeFunction == EAnalyzeFunction.AutoCorrelation) - { - _AnalyzeToneFunc = _GetAutoCorrelation; - _MinWeightDiff = 0.0001; - } -#endif #if TEST_PITCH _TestPitchDetection(); #endif @@ -106,23 +56,6 @@ namespace Vocaluxe.Lib.Sound.Record _Dispose(); } - private static void _Init() - { - _InitCount++; - if (_InitCount > 1) - return; //Do init only once -#if USE_NATIVE_DETECTION - CFastAnalyzer.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; } public int Tone { get; set; } @@ -134,7 +67,7 @@ namespace Vocaluxe.Lib.Sound.Record public bool ToneValid { get; private set; } - public float[] ToneWeigth { get; private set; } + public float[] ToneWeigths { get; private set; } /// /// Minimum volume for a tone to be valid @@ -143,21 +76,11 @@ namespace Vocaluxe.Lib.Sound.Record public void Reset() { - lock (_Stream) - { - _Stream.SetLength(0L); - _NewSamples = false; - } ToneValid = false; ToneAbs = 0; Tone = 0; - for (int i = 0; i < ToneWeigth.Length; i++) - ToneWeigth[i] = 0f; - } - - private void _Add(byte[] bytes) - { - _Stream.Write(bytes, 0, bytes.Length); + for (int i = 0; i < ToneWeigths.Length; i++) + ToneWeigths[i] = 0f; } public void ProcessNewBuffer(byte[] buffer) @@ -168,168 +91,24 @@ namespace Vocaluxe.Lib.Sound.Record // voice passthrough (send data to playback-device) //if (assigned(fVoiceStream)) then //fVoiceStream.WriteData(Buffer, BufferSize); - lock (_Stream) - { - _Add(buffer); - _NewSamples = true; - } - //_Analyzer.Input(buffer); + _Analyzer.Input(buffer); } public void AnalyzeBuffer() { - if (!_NewSamples) - return; - - /*_NewSamples = false; _Analyzer.Process(); _MaxVolume = _Analyzer.GetPeak() / 43 + 1; - CTone tone = _Analyzer.FindTone(); - if (tone != null) + int tone = (int)Math.Round(_Analyzer.FindNote()); + if (tone >= 0) { - ToneAbs = tone.Note; + ToneAbs = tone; Tone = ToneAbs % 12; ToneValid = true; } - else - ToneValid = false;*/ - - lock (_Stream) - { - if (_Stream.Length >= _AnalysisByteBufLen) - { - _Stream.Position -= _AnalysisByteBufLen; - _Stream.Read(_AnalysisByteBuffer, 0, _AnalysisByteBufLen); - } - _NewSamples = false; - } - - Buffer.BlockCopy(_AnalysisByteBuffer, 0, _AnalysisBuffer, 0, _AnalysisByteBufLen); - - try - { - // find maximum volume - _FindMaxVolume(); - - if (MaxVolume >= MinVolume) - _AnalyzeTones(); - else - ToneValid = false; - } - catch (Exception) {} - } - - private void _FindMaxVolume() - { - int maxVolume = 0; - for (int i = 0; i < _AnalysisBufLen / 4; i++) - { - int volume = _AnalysisBuffer[i]; - if (volume < 0) - volume = -volume; - if (volume > maxVolume) - maxVolume = volume; - } - _MaxVolume = (double)maxVolume / Int16.MaxValue; - } - - private void _AnalyzeTones() - { -#if USE_NATIVE_DETECTION - int maxTone = CFastAnalyzer.GetTone(_AnalysisBuffer, _TmpWeights); - if (maxTone >= 0) -#else - // prepare to analyze - double maxWeight = -1.0; - double minWeight = 1.0; - int maxTone = -1; - - - // analyze halftones - // Note: at the lowest tone (~65Hz) and a buffer-size of 4096 - // at 44.1 (or 48kHz) only 6 (or 5) samples are compared, this might be - // too few samples -> use a bigger buffer-size - for (int toneIndex = _HalfToneMin; toneIndex <= _HalfToneMax; toneIndex++) - { - double curWeight = _AnalyzeToneFunc(toneIndex); - - if (curWeight > maxWeight) - { - maxWeight = curWeight; - maxTone = toneIndex; - } - - if (curWeight < minWeight) - minWeight = curWeight; - - _TmpWeights[toneIndex - _HalfToneMin] = (float)curWeight; - } - - if (maxWeight - minWeight > _MinWeightDiff) -#endif - { - Array.Copy(_TmpWeights, ToneWeigth, NumHalfTones); - - ToneAbs = maxTone; - //Console.WriteLine(maxTone); - Tone = maxTone % 12; - ToneValid = true; - } else ToneValid = false; } -#if !USE_NATIVE_DETECTION - private double _GetAutoCorrelation(int tone) - { - double samplesPerPeriodD = _SamplesPerPeriodPerTone[tone]; // 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 < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++) - { - // calc distance to corresponding sample in next period (lower means more distant) - double dist = _AnalysisBuffer[sampleIndex] * - (_AnalysisBuffer[correlatingSampleIndex] * fLow + _AnalysisBuffer[correlatingSampleIndex + 1] * fHigh); - accumDist += dist; - } - - //Using _AnalysisBufLen here makes it return correct values among all analyzed frequencies - const double scaleValue = (double)Int16.MaxValue * (double)Int16.MaxValue * _AnalysisBufLen; - return accumDist / scaleValue; - } - - private double _GetAmdf(int tone) - { - double samplesPerPeriodD = _SamplesPerPeriodPerTone[tone]; // 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 < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++) - { - // calc distance (correlation: 1-dist/IntMax*2) to corresponding sample in next period (0=equal .. IntMax*2=totally different) - //int dist = (int)Math.Abs(_AnalysisBuffer[sampleIndex] - _AnalysisBuffer[correlatingSampleIndex]); - double dist = Math.Abs(_AnalysisBuffer[sampleIndex] - - (_AnalysisBuffer[correlatingSampleIndex] * fLow + _AnalysisBuffer[correlatingSampleIndex + 1] * fHigh)); - accumDist += dist; - } - //Use analyzed sample count here. BufLen yields more errors - return 1.0 - accumDist / Int16.MaxValue / sampleIndex; - } -#endif - public void Dispose() { _Dispose(); @@ -338,17 +117,11 @@ namespace Vocaluxe.Lib.Sound.Record private void _Dispose() { - if (_Stream != null) + if (_Analyzer != null) { - _Stream.Dispose(); - _Stream = null; + _Analyzer.Dispose(); + _Analyzer = null; } -#if USE_NATIVE_DETECTION - Debug.Assert(_InitCount > 0); - _InitCount--; - if (_InitCount == 0) - CFastAnalyzer.DeInit(); -#endif } #if TEST_PITCH @@ -393,11 +166,9 @@ namespace Vocaluxe.Lib.Sound.Record } } _GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, 5), 44100, ref angle, out data); - ProcessNewBuffer(data); - AnalyzeBuffer(); Stopwatch sw = new Stopwatch(); sw.Start(); - const int repeats = 2000; + const int repeats = 1000; for (int i = 0; i < repeats; i++) { ProcessNewBuffer(data); @@ -486,7 +257,7 @@ namespace Vocaluxe.Lib.Sound.Record private static void _GetSineWave(double freq, int sampleRate, ref double angle, out byte[] data) { const short max = short.MaxValue; - const int len = _AnalysisBufLen; //sampleRate * durationMs / 1000; + const int len = 4096; //sampleRate * durationMs / 1000; short[] data16Bit = new short[len]; for (int i = 0; i < len; i++) data16Bit[i] = (short)(Math.Sin(2 * Math.PI * i / sampleRate * freq + angle) * max); diff --git a/Vocaluxe/Lib/Sound/Record/CRecordBase.cs b/Vocaluxe/Lib/Sound/Record/CRecordBase.cs index 78630c90..074a7711 100644 --- a/Vocaluxe/Lib/Sound/Record/CRecordBase.cs +++ b/Vocaluxe/Lib/Sound/Record/CRecordBase.cs @@ -137,7 +137,7 @@ namespace Vocaluxe.Lib.Sound.Record public float[] ToneWeigth(int player) { - return _Initialized ? _Buffer[player].ToneWeigth : null; + return _Initialized ? _Buffer[player].ToneWeigths : null; } public ReadOnlyCollection RecordDevices()