diff --git a/PitchTracker/PitchWrapper.cpp b/PitchTracker/PitchWrapper.cpp index ff6e4252..6314b64c 100644 --- a/PitchTracker/PitchWrapper.cpp +++ b/PitchTracker/PitchWrapper.cpp @@ -39,17 +39,17 @@ void Analyzer_Process(Analyzer* analyzer){ analyzer->process(); } -double Analyzer_GetPeak(Analyzer* analyzer){ +float Analyzer_GetPeak(Analyzer* analyzer){ if(!analyzer) return -999; - return analyzer->getPeak(); + return static_cast(analyzer->getPeakRaw()); } double Analyzer_FindNote(Analyzer* analyzer, double minFreq, double maxFreq){ if(!analyzer) return -1; const Tone* tone = analyzer->findTone(minFreq, maxFreq); - return (tone == NULL) ? -1 : FreqToNote(tone->freq); + return (tone == NULL) ? -1 : FreqToNote(tone->freq); } bool Analyzer_OutputFloat(Analyzer* analyzer, float* data, int sampleCt, float rate){ @@ -71,6 +71,18 @@ int PtAKF_GetNumHalfTones(){ return PtAKF::GetNumHalfTones(); } +void PtAKF_SetVolumeTreshold(PtAKF* analyzer, float threshold){ + if(!analyzer) + return; + analyzer->SetVolumeThreshold(threshold); +} + +float PtAKF_GetVolumeThreshold(PtAKF* analyzer){ + if(!analyzer) + return 0.f; + return analyzer->GetVolumeThreshold(); +} + void PtAKF_InputByte(PtAKF* analyzer, char* data, int sampleCt){ if(sampleCt <= 0 || !analyzer) return; @@ -82,7 +94,7 @@ void PtAKF_InputByte(PtAKF* analyzer, char* data, int sampleCt){ //analyzer->input(dataShort, dataShort + sampleCt); } -int PtAKF_GetNote(PtAKF* analyzer, double* maxVolume, float* weights){ +int PtAKF_GetNote(PtAKF* analyzer, float* maxVolume, float* weights){ if(!analyzer) return -1; return analyzer->GetNote(maxVolume, weights); @@ -97,6 +109,18 @@ void PtDyWa_Free(PtDyWa* analyzer){ delete analyzer; } +void PtDyWa_SetVolumeTreshold(PtDyWa* analyzer, float threshold){ + if(!analyzer) + return; + analyzer->SetVolumeThreshold(threshold); +} + +float PtDyWa_GetVolumeThreshold(PtDyWa* analyzer){ + if(!analyzer) + return 0.f; + return analyzer->GetVolumeThreshold(); +} + void PtDyWa_InputByte(PtDyWa* analyzer, char* data, int sampleCt){ if(sampleCt <= 0 || !analyzer) return; @@ -105,16 +129,10 @@ void PtDyWa_InputByte(PtDyWa* analyzer, char* data, int sampleCt){ freeFloatArray(dataFloat); } -void PtDyWa_Process(PtDyWa* analyzer){ - if(!analyzer) - return; - analyzer->Process(); -} - -double PtDyWa_GetNote(PtDyWa* analyzer){ +double PtDyWa_FindNote(PtDyWa* analyzer, float* maxVolume){ if(!analyzer) return -1; - return analyzer->GetNote(); + return analyzer->FindNote(maxVolume); } /* diff --git a/PitchTracker/PitchWrapper.h b/PitchTracker/PitchWrapper.h index 80c5cfa7..6425f862 100644 --- a/PitchTracker/PitchWrapper.h +++ b/PitchTracker/PitchWrapper.h @@ -12,21 +12,24 @@ 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 float 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 PtAKF* PtAKF_Create(unsigned step); DllExport void PtAKF_Free(PtAKF* analyzer); DllExport int PtAKF_GetNumHalfTones(); +DllExport void PtAKF_SetVolumeThreshold(PtAKF* analyzer, float threshold); +DllExport float PtAKF_GetVolumeThreshold(PtAKF* analyzer); DllExport void PtAKF_InputByte(PtAKF* analyzer, char* data, int sampleCt); -DllExport int PtAKF_GetNote(PtAKF* analyzer, double* maxVolume, float* weights); +DllExport int PtAKF_GetNote(PtAKF* analyzer, float* maxVolume, float* weights); DllExport PtDyWa* PtDyWa_Create(unsigned step); DllExport void PtDyWa_Free(PtDyWa* analyzer); +DllExport void PtDyWa_SetVolumeTreshold(PtDyWa* analyzer,float threshold); +DllExport float PtDyWa_GetVolumeThreshold(PtDyWa* analyzer); DllExport void PtDyWa_InputByte(PtDyWa* analyzer, char* data, int sampleCt); -DllExport void PtDyWa_Process(PtDyWa* analyzer); -DllExport double PtDyWa_GetNote(PtDyWa* analyzer); +DllExport double PtDyWa_FindNote(PtDyWa* analyzer); /* public ref class CTone { diff --git a/PitchTracker/dywapitchtrack/dywapitchtrack.cpp b/PitchTracker/dywapitchtrack/dywapitchtrack.cpp index f4c6e653..ec17f052 100644 --- a/PitchTracker/dywapitchtrack/dywapitchtrack.cpp +++ b/PitchTracker/dywapitchtrack/dywapitchtrack.cpp @@ -102,7 +102,7 @@ typedef struct _minmax { struct _minmax *next; } minmax; -double _dywapitch_computeWaveletPitch(double * samples, int startsample, int samplecount) { +double _dywapitch_computeWaveletPitch(double * samples, int startsample, int samplecount, float* maxVolume, double volThreshold) { double pitchF = 0.0; int i, j; @@ -143,6 +143,9 @@ double _dywapitch_computeWaveletPitch(double * samples, int startsample, int sam maxValue = maxValue - theDC; minValue = minValue - theDC; double amplitudeMax = (maxValue > -minValue ? maxValue : -minValue); + *maxVolume = static_cast(amplitudeMax + theDC); + if(*maxVolume < volThreshold) + return 0.0; ampltitudeThreshold = amplitudeMax*maximaThresholdRatio; //asLog("dywapitch theDC=%f ampltitudeThreshold=%f\n", theDC, ampltitudeThreshold); @@ -439,8 +442,8 @@ void dywapitch_inittracking(dywapitchtracker *pitchtracker) { pitchtracker->_pitchConfidence = -1; } -double dywapitch_computepitch(dywapitchtracker *pitchtracker, double * samples, int startsample, int samplecount) { - double raw_pitch = _dywapitch_computeWaveletPitch(samples, startsample, samplecount); +double dywapitch_computepitch(dywapitchtracker *pitchtracker, double * samples, int startsample, int samplecount, float* maxVolume, double volThreshold) { + double raw_pitch = _dywapitch_computeWaveletPitch(samples, startsample, samplecount, maxVolume, volThreshold); return _dywapitch_dynamicprocess(pitchtracker, raw_pitch); } diff --git a/PitchTracker/dywapitchtrack/dywapitchtrack.h b/PitchTracker/dywapitchtrack/dywapitchtrack.h index 39aeb38d..95767c29 100644 --- a/PitchTracker/dywapitchtrack/dywapitchtrack.h +++ b/PitchTracker/dywapitchtrack/dywapitchtrack.h @@ -100,7 +100,7 @@ void dywapitch_inittracking(dywapitchtracker *pitchtracker); // startsample : the index of the first sample to use in the sample buffer // samplecount : the number of samples to use to compte the pitch // return 0.0 if no pitch was found (sound too low, noise, etc..) -double dywapitch_computepitch(dywapitchtracker *pitchtracker, double * samples, int startsample, int samplecount); +double dywapitch_computepitch(dywapitchtracker *pitchtracker, double * samples, int startsample, int samplecount, float* maxVolume, double volThreshold); #ifdef __cplusplus } // extern "C" diff --git a/PitchTracker/dywapitchtrack/ptDyWa.cpp b/PitchTracker/dywapitchtrack/ptDyWa.cpp index fa3ab4f0..96572e27 100644 --- a/PitchTracker/dywapitchtrack/ptDyWa.cpp +++ b/PitchTracker/dywapitchtrack/ptDyWa.cpp @@ -5,23 +5,27 @@ PtDyWa::PtDyWa(unsigned step){ _Step = step; dywapitch_inittracking(&_State); + _VolTreshold = 0.01f; + _LastMaxVol = 0.f; } -void PtDyWa::Process(){ +void PtDyWa::SetVolumeThreshold(float threshold){ + _VolTreshold = threshold; +} + +double PtDyWa::FindNote(float* maxVolume){ double AnaylsisBuf[_SampleCt]; size_t size = _AnalysisBuf.size(); if(size > _SampleCt) _AnalysisBuf.pop(size - _SampleCt); - if(!_AnalysisBuf.read(AnaylsisBuf, AnaylsisBuf + _SampleCt)) - return; + if(!_AnalysisBuf.read(AnaylsisBuf, AnaylsisBuf + _SampleCt)){ + *maxVolume = _LastMaxVol * 0.85f; + return -1; + } _AnalysisBuf.pop(_Step); - double pitch = dywapitch_computepitch(&_State, AnaylsisBuf,0,_SampleCt); + double pitch = dywapitch_computepitch(&_State, AnaylsisBuf, 0, _SampleCt, maxVolume, _VolTreshold); if(pitch == 0.0) - _LastNote = -1; + return -1; else - _LastNote = FreqToNote(pitch); -} - -double PtDyWa::GetNote(){ - return _LastNote; + return FreqToNote(pitch); } \ No newline at end of file diff --git a/PitchTracker/dywapitchtrack/ptDyWa.h b/PitchTracker/dywapitchtrack/ptDyWa.h index c66e1ac7..dd0eb615 100644 --- a/PitchTracker/dywapitchtrack/ptDyWa.h +++ b/PitchTracker/dywapitchtrack/ptDyWa.h @@ -11,12 +11,14 @@ public: _AnalysisBuf.insert(begin, end); } - void Process(); - double GetNote(); + double FindNote(float* maxVolume); + void SetVolumeThreshold(float threshold); + float GetVolumeThreshold(){return _VolTreshold;} private: dywapitchtracker _State; constexpr static size_t _SampleCt = 2048; RingBuffer<_SampleCt * 2> _AnalysisBuf; unsigned _Step; - double _LastNote; + float _VolTreshold; + float _LastMaxVol; }; \ No newline at end of file diff --git a/PitchTracker/performous/pitch.hh b/PitchTracker/performous/pitch.hh index 1108e0b9..f87a7638 100644 --- a/PitchTracker/performous/pitch.hh +++ b/PitchTracker/performous/pitch.hh @@ -86,6 +86,7 @@ public: fft_t const& getFFT() const { return m_fft; } /** Get the peak level in dB (negative value, 0.0 = clipping). **/ double getPeak() const { return 10.0 * log10(m_peak); } + double getPeakRaw() const { return m_peak; } /** Get a list of all tones detected. **/ tones_t const& getTones() const { return m_tones; } /** Find a tone within the singing range; prefers strong tones around 200-400 Hz. **/ diff --git a/PitchTracker/ptAKF.cpp b/PitchTracker/ptAKF.cpp index f5f95bf7..7a418f22 100644 --- a/PitchTracker/ptAKF.cpp +++ b/PitchTracker/ptAKF.cpp @@ -47,6 +47,8 @@ PtAKF::PtAKF(unsigned step){ } } _Step = step; + _VolTreshold = 0.01f; + _LastMaxVol = 0.f; } PtAKF::~PtAKF(){ @@ -59,13 +61,19 @@ PtAKF::~PtAKF(){ } } -int PtAKF::GetNote(double* restrict maxVolume, float* restrict weights){ +void PtAKF::SetVolumeThreshold(float threshold){ + _VolTreshold = threshold; +} + +int PtAKF::GetNote(float* restrict maxVolume, float* restrict weights){ float AnaylsisBuf[_SampleCt]; size_t size = _AnalysisBuf.size(); if(size > _SampleCt) _AnalysisBuf.pop(size - _SampleCt); - if(!_AnalysisBuf.read(AnaylsisBuf, AnaylsisBuf + _SampleCt)) + if(!_AnalysisBuf.read(AnaylsisBuf, AnaylsisBuf + _SampleCt)){ + *maxVolume = _LastMaxVol * 0.85f; return -1; + } _AnalysisBuf.pop(_Step); return _GetNote(AnaylsisBuf, maxVolume, weights); } @@ -96,7 +104,7 @@ void PtAKF::AddPeak(SPeak peaks[], float curWeight, int toneIndex){ } } -int PtAKF::_GetNote(float* restrict samples, double* restrict maxVolume, float* restrict weights){ +int PtAKF::_GetNote(float* restrict samples, float* restrict maxVolume, float* restrict weights){ // Calculate maximum volume float maxVolumeL = 0; @@ -105,9 +113,12 @@ int PtAKF::_GetNote(float* restrict samples, double* restrict maxVolume, float* if(vol > maxVolumeL) maxVolumeL = vol; } - //maxVolumeL /= 32767.; + //maxVolumeL /= 32767.f; *maxVolume = maxVolumeL; + if(maxVolumeL < _VolTreshold) + return -1; + float samplesWindowed[_SampleCt]; for(int i = 0; i < _SampleCt; i++){ diff --git a/PitchTracker/ptAKF.h b/PitchTracker/ptAKF.h index 02a2baf2..58c04433 100644 --- a/PitchTracker/ptAKF.h +++ b/PitchTracker/ptAKF.h @@ -29,7 +29,9 @@ public: _AnalysisBuf.insert(begin, end); } - int GetNote(double* restrict maxVolume, float* restrict weights); + int GetNote(float* restrict maxVolume, float* restrict weights); + void SetVolumeThreshold(float threshold); + float GetVolumeThreshold(){return _VolTreshold;} static int GetNumHalfTones(){ return _MaxHalfTone - _MinHalfTone + 1;} private: @@ -48,8 +50,11 @@ private: constexpr static size_t _SampleCt = 2048; RingBuffer<_SampleCt * 2> _AnalysisBuf; unsigned _Step; + float _VolTreshold; + float _LastMaxVol; + + int _GetNote(float* restrict samples, float* restrict maxVolume, float* restrict weights); - static int _GetNote(float* restrict samples, double* restrict maxVolume, float* restrict weights); static float _AnalyzeBySampleCt(float* restrict samples, float* restrict samplesWindowed, float samplesPerPeriodD); static inline float _AnalyzeByTone(float* restrict samples, float* restrict samplesWindowed, int toneIndex); static float _AKFBySampleCt(float* restrict samples, float samplesPerPeriodD); diff --git a/Vocaluxe/Lib/Sound/Record/CBuffer.cs b/Vocaluxe/Lib/Sound/Record/CBuffer.cs index 755aa118..9a4a7b4d 100644 --- a/Vocaluxe/Lib/Sound/Record/CBuffer.cs +++ b/Vocaluxe/Lib/Sound/Record/CBuffer.cs @@ -24,6 +24,8 @@ using System.IO; using System.Windows.Forms; using System.Diagnostics; using Pitch; +using Vocaluxe.Lib.Sound.Record.Pitch; +using Vocaluxe.Lib.Sound.Record.PitchTracker; using VocaluxeLib; using VocaluxeLib.Utils; @@ -38,17 +40,18 @@ namespace Vocaluxe.Lib.Sound.Record 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) #endif - private CPtAKF _Analyzer = new CPtAKF(); + private CPitchTracker _PitchTracker = new CPtAKF(); - private double _MaxVolume; + private float _MaxVolume; public CBuffer() { - MinVolume = 0.02f; + VolTreshold = 0.02f; ToneWeigths = new float[GetNumHalfTones()]; Reset(); #if TEST_PITCH _TestPitchDetection(); + Reset(); #endif } @@ -57,9 +60,9 @@ namespace Vocaluxe.Lib.Sound.Record _Dispose(); } - public static int GetNumHalfTones() + public int GetNumHalfTones() { - return CPtAKF.GetNumHalfTones(); + return _PitchTracker.GetNumHalfTones(); } public int ToneAbs { get; private set; } @@ -68,7 +71,7 @@ namespace Vocaluxe.Lib.Sound.Record public float MaxVolume { - get { return (float)_MaxVolume; } + get { return _MaxVolume; } } public bool ToneValid { get; private set; } @@ -78,7 +81,11 @@ namespace Vocaluxe.Lib.Sound.Record /// /// Minimum volume for a tone to be valid /// - public float MinVolume { get; set; } + public float VolTreshold + { + get { return _PitchTracker.VolumeTreshold; } + set { _PitchTracker.VolumeTreshold = value; } + } public void Reset() { @@ -97,14 +104,14 @@ namespace Vocaluxe.Lib.Sound.Record // voice passthrough (send data to playback-device) //if (assigned(fVoiceStream)) then //fVoiceStream.WriteData(Buffer, BufferSize); - _Analyzer.Input(buffer); + _PitchTracker.Input(buffer); } public void AnalyzeBuffer() { //_MaxVolume = _Analyzer.GetPeak() / 43 + 1; - int tone = _Analyzer.GetNote(out _MaxVolume, ToneWeigths); //(int)Math.Round(_Analyzer.FindNote()); // - if (tone >= 0 && _MaxVolume >= MinVolume) + int tone = _PitchTracker.GetNote(out _MaxVolume, ToneWeigths); //(int)Math.Round(_Analyzer.FindNote()); // + if (tone >= 0) { ToneAbs = tone; Tone = ToneAbs % 12; @@ -122,10 +129,10 @@ namespace Vocaluxe.Lib.Sound.Record private void _Dispose() { - if (_Analyzer != null) + if (_PitchTracker != null) { - _Analyzer.Dispose(); - _Analyzer = null; + _PitchTracker.Dispose(); + _PitchTracker = null; } } @@ -288,11 +295,13 @@ namespace Vocaluxe.Lib.Sound.Record int curTimeIndex = -1; int curNote = -1; const int maxSamplesPerBatch = 512; - CAnalyzer analyzer2 = new CAnalyzer(); - CPtDyWa analyzer3 = new CPtDyWa(); - PitchTracker analyzer4 = new PitchTracker {SampleRate = 44100, DetectLevelThreshold = 0.001f}; + CPitchTracker analyzer2 = new CAnalyzer(); + CPitchTracker analyzer3 = new CPtDyWa(); + CPitchTracker analyzer4 = new CPtSharp(); int[] detectedTones = new int[4]; int[] oks = new int[4]; + float dummyMaxVol; + float[] dummyWeights = new float[ToneWeigths.Length]; int lCt = 0; while (wavFile.NumSamplesLeft > maxSamplesPerBatch) { @@ -303,14 +312,7 @@ namespace Vocaluxe.Lib.Sound.Record AnalyzeBuffer(); analyzer2.Input(samples); analyzer3.Input(samples); - analyzer2.Process(); - analyzer3.Process(); - short[] samplesShort = new short[samples.Length / 2]; - Buffer.BlockCopy(samples, 0, samplesShort, 0, samples.Length); - float[] samplesFloat = new float[samplesShort.Length]; - for (int i = 0; i < samplesShort.Length; i++) - samplesFloat[i] = (float)samplesShort[i] / short.MaxValue; - analyzer4.ProcessBuffer(samplesFloat); + analyzer4.Input(samples); while (curTimeIndex + 1 < tones.Count && time >= tones[curTimeIndex + 1].Time) { curTimeIndex++; @@ -319,9 +321,9 @@ namespace Vocaluxe.Lib.Sound.Record if (curNote < 0) continue; detectedTones[0] = ToneValid ? ToneAbs : -1; - detectedTones[1] = (int)Math.Round(analyzer2.FindNote(64, 1770)); - detectedTones[2] = (int)Math.Round(analyzer3.GetNote()); - detectedTones[3] = analyzer4.CurrentPitchRecord.MidiNote - 15 - 21; + detectedTones[1] = analyzer2.GetNote(out dummyMaxVol, dummyWeights); + detectedTones[2] = analyzer3.GetNote(out dummyMaxVol, dummyWeights); + detectedTones[3] = analyzer4.GetNote(out dummyMaxVol, dummyWeights); lCt++; /*if (!ToneValid) CBase.Log.LogDebug("Note " + _ToneToNote(curNote) + " at " + time + "ms not detected"); diff --git a/Vocaluxe/Lib/Sound/Record/CRecordBase.cs b/Vocaluxe/Lib/Sound/Record/CRecordBase.cs index f68fc268..5dd64987 100644 --- a/Vocaluxe/Lib/Sound/Record/CRecordBase.cs +++ b/Vocaluxe/Lib/Sound/Record/CRecordBase.cs @@ -132,7 +132,7 @@ namespace Vocaluxe.Lib.Sound.Record public int NumHalfTones() { - return CBuffer.GetNumHalfTones(); + return _Buffer[0].GetNumHalfTones(); } public float[] ToneWeigth(int player) diff --git a/Vocaluxe/Lib/Sound/Record/Pitch/PitchDsp.cs b/Vocaluxe/Lib/Sound/Record/Pitch/PitchDsp.cs index b04f5bbf..0eb79432 100644 --- a/Vocaluxe/Lib/Sound/Record/Pitch/PitchDsp.cs +++ b/Vocaluxe/Lib/Sound/Record/Pitch/PitchDsp.cs @@ -28,7 +28,6 @@ namespace Pitch private int m_blockLen34; // 3/4 block len private int m_blockLen44; // 4/4 block len private double m_sampleRate; - private float m_detectLevelThreshold; private int m_numCourseSteps; private float[] m_pCourseFreqOffset; @@ -45,12 +44,11 @@ namespace Pitch /// /// /// - public PitchDsp(double sampleRate, float minPitch, float maxPitch, float detectLevelThreshold) + public PitchDsp(double sampleRate, float minPitch, float maxPitch) { m_sampleRate = sampleRate; m_minPitch = minPitch; m_maxPitch = maxPitch; - m_detectLevelThreshold = detectLevelThreshold; m_minNote = (int)(PitchToMidiNote(m_minPitch) + 0.5f) + 2; m_maxNote = (int)(PitchToMidiNote(m_maxPitch) + 0.5f) - 2; @@ -117,19 +115,9 @@ namespace Pitch /// /// Detect the pitch /// - public float DetectPitch(float[] samplesLo, float[] samplesHi, int numSamples) + public float DetectPitch(float[] samplesLo, float[] samplesHi) { - float pitch = 0.0f; - - if (!LevelIsAbove(samplesLo, numSamples, m_detectLevelThreshold) && - !LevelIsAbove(samplesHi, numSamples, m_detectLevelThreshold)) - { - // Level is too low - return 0.0f; - } - - pitch = DetectPitchLo(samplesLo, samplesHi); - return pitch; + return DetectPitchLo(samplesLo, samplesHi); } /// diff --git a/Vocaluxe/Lib/Sound/Record/Pitch/PitchTracker.cs b/Vocaluxe/Lib/Sound/Record/Pitch/PitchTracker.cs index d496ea16..8e7e6776 100644 --- a/Vocaluxe/Lib/Sound/Record/Pitch/PitchTracker.cs +++ b/Vocaluxe/Lib/Sound/Record/Pitch/PitchTracker.cs @@ -1,14 +1,14 @@ using System; using System.Collections.Generic; -using System.Text; using System.Collections; -using System.Threading; +using Pitch; -namespace Pitch +namespace Vocaluxe.Lib.Sound.Record.Pitch { /// /// Tracks pitch /// + // ReSharper disable InconsistentNaming public class PitchTracker { private const int kOctaveSteps = 96; @@ -50,6 +50,8 @@ namespace Pitch private IIRFilter m_iirFilterHiLo; private IIRFilter m_iirFilterHiHi; + public float LastMaxVol = 0; + public delegate void PitchDetectedHandler(PitchTracker sender, PitchRecord pitchRecord); public event PitchDetectedHandler PitchDetected; @@ -87,8 +89,9 @@ namespace Pitch return; m_detectLevelThreshold = newValue; - Setup(); + //Setup(); } + get { return m_detectLevelThreshold; } } /// @@ -229,19 +232,20 @@ namespace Pitch m_circularBufferHi.Available = m_detectOverlapSamples; } - /// - /// Process the passed in buffer of data. During this call, the PitchDetected event will - /// be fired zero or more times, depending how many pitch records will fit in the new - /// and previously cached buffer. - /// - /// This means that there is no size restriction on the buffer that is passed into ProcessBuffer. - /// For instance, ProcessBuffer can be called with one very large buffer that contains all of the - /// audio to be processed (many PitchDetected events will be fired), or just a small buffer at - /// a time which is more typical for realtime applications. In the latter case, the PitchDetected - /// event might not be fired at all since additional calls must first be made to accumulate enough - /// data do another pitch detect operation. - /// - /// Input buffer. Samples must be in the range -1.0 to 1.0 + /// + /// Process the passed in buffer of data. During this call, the PitchDetected event will + /// be fired zero or more times, depending how many pitch records will fit in the new + /// and previously cached buffer. + /// + /// This means that there is no size restriction on the buffer that is passed into ProcessBuffer. + /// For instance, ProcessBuffer can be called with one very large buffer that contains all of the + /// audio to be processed (many PitchDetected events will be fired), or just a small buffer at + /// a time which is more typical for realtime applications. In the latter case, the PitchDetected + /// event might not be fired at all since additional calls must first be made to accumulate enough + /// data do another pitch detect operation. + /// + /// Input buffer. Samples must be in the range -1.0 to 1.0 + /// /// Number of samples to process. Zero means all samples in the buffer public void ProcessBuffer(float[] inBuffer, int sampleCount = 0) { @@ -251,6 +255,8 @@ namespace Pitch int samplesProcessed = 0; int srcLength = sampleCount == 0 ? inBuffer.Length : Math.Min(sampleCount, inBuffer.Length); + float maxVolume = LastMaxVol * 0.85f; + while (samplesProcessed < srcLength) { int frameCount = Math.Min(srcLength - samplesProcessed, m_pitchBufSize + m_detectOverlapSamples); @@ -268,12 +274,21 @@ namespace Pitch while (m_circularBufferLo.ReadBuffer(m_pitchBufLo, m_curPitchSamplePos, m_pitchBufSize + m_detectOverlapSamples)) { float pitch1; - float pitch2 = 0.0f; + float pitch2; float detectedPitch = 0.0f; m_circularBufferHi.ReadBuffer(m_pitchBufHi, m_curPitchSamplePos, m_pitchBufSize + m_detectOverlapSamples); - pitch1 = m_dsp.DetectPitch(m_pitchBufLo, m_pitchBufHi, m_pitchBufSize); + maxVolume = 0f; + for (int i = 0; i < (m_pitchBufSize + m_detectOverlapSamples) / 2; i++) + { + if (maxVolume < m_pitchBufHi[i]) + maxVolume = m_pitchBufHi[i]; + if (maxVolume < m_pitchBufLo[i]) + maxVolume = m_pitchBufLo[i]; + } + + pitch1 = (maxVolume >= m_detectLevelThreshold) ? m_dsp.DetectPitch(m_pitchBufLo, m_pitchBufHi) : 0f; if (pitch1 > 0.0f) { @@ -281,7 +296,7 @@ namespace Pitch m_pitchBufLo.Copy(m_pitchBufLo, m_detectOverlapSamples - 1, 0, m_pitchBufSize); m_pitchBufHi.Copy(m_pitchBufHi, m_detectOverlapSamples - 1, 0, m_pitchBufSize); - pitch2 = m_dsp.DetectPitch(m_pitchBufLo, m_pitchBufHi, m_pitchBufSize); + pitch2 = m_dsp.DetectPitch(m_pitchBufLo, m_pitchBufHi); if (pitch2 > 0.0f) { @@ -301,6 +316,7 @@ namespace Pitch samplesProcessed += frameCount; } + LastMaxVol = maxVolume; } /// @@ -311,7 +327,7 @@ namespace Pitch if (m_sampleRate < 1.0f) return; - m_dsp = new PitchDsp(m_sampleRate, kMinFreq, kMaxFreq, m_detectLevelThreshold); + m_dsp = new PitchDsp(m_sampleRate, kMinFreq, kMaxFreq); m_iirFilterLoLo = new IIRFilter(); m_iirFilterLoLo.Proto = IIRFilter.ProtoType.Butterworth; @@ -411,4 +427,6 @@ namespace Pitch public int MidiCents { get; set; } } } + + // ReSharper restore InconsistentNaming } \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Record/CAnalyzer.cs b/Vocaluxe/Lib/Sound/Record/PitchTracker/CAnalyzer.cs similarity index 55% rename from Vocaluxe/Lib/Sound/Record/CAnalyzer.cs rename to Vocaluxe/Lib/Sound/Record/PitchTracker/CAnalyzer.cs index 9f9529e3..fcb45fa8 100644 --- a/Vocaluxe/Lib/Sound/Record/CAnalyzer.cs +++ b/Vocaluxe/Lib/Sound/Record/PitchTracker/CAnalyzer.cs @@ -1,13 +1,13 @@ using System; using System.Runtime.InteropServices; -namespace Vocaluxe.Lib.Sound.Record +namespace Vocaluxe.Lib.Sound.Record.PitchTracker { /// /// Analyzer from Performous /// Should be very reliable but needs some proof (artificial tests show some errors) /// - class CAnalyzer : IDisposable + class CAnalyzer : CPitchTracker { #region Imports [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] @@ -29,7 +29,7 @@ namespace Vocaluxe.Lib.Sound.Record private static extern void Analyzer_Process(IntPtr analyzer); [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] - private static extern double Analyzer_GetPeak(IntPtr analyzer); + private static extern float Analyzer_GetPeak(IntPtr analyzer); [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] private static extern double Analyzer_FindNote(IntPtr analyzer, double minFreq, double maxFreq); @@ -39,45 +39,28 @@ namespace Vocaluxe.Lib.Sound.Record #endregion private IntPtr _Instance; + private float _VolumeTreshold; public CAnalyzer(uint step = 200) { _Instance = Analyzer_Create(step); + _VolumeTreshold = 0.01f; } - ~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) + public override void Input(byte[] data) { Analyzer_InputByte(_Instance, data, data.Length / 2); } - public void Process() + public override int GetNote(out float maxVolume, float[] weights) { 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); + maxVolume = Analyzer_GetPeak(_Instance); + if (maxVolume < _VolumeTreshold) + return -1; + int note = (int)Math.Round(Analyzer_FindNote(_Instance, 60, 1800)); + _SetWeights(note, weights); + return note; } public bool Output(float[] data, float rate) @@ -85,7 +68,7 @@ namespace Vocaluxe.Lib.Sound.Record return Analyzer_OutputFloat(_Instance, data, data.Length, rate); } - private void _Dispose(bool disposing) + protected override void _Dispose(bool disposing) { if (_Instance == IntPtr.Zero) throw new ObjectDisposedException(GetType().Name); @@ -93,10 +76,15 @@ namespace Vocaluxe.Lib.Sound.Record _Instance = IntPtr.Zero; } - public void Dispose() + public override int GetNumHalfTones() { - _Dispose(true); - GC.SuppressFinalize(this); + return NumHalfTonesDef; + } + + public override float VolumeTreshold + { + get { return _VolumeTreshold; } + set { _VolumeTreshold = value; } } } @@ -104,7 +92,7 @@ namespace Vocaluxe.Lib.Sound.Record /// Pitchtracker (Pt) that uses autocorrelation (AKF) and AMDF /// Quite fast and perfect in artificial tests, but may fail in real world scenarios (e.g. missing fundamental) /// - class CPtAKF : IDisposable + class CPtAKF : CPitchTracker { #region Imports [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] @@ -113,14 +101,20 @@ namespace Vocaluxe.Lib.Sound.Record [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void PtAKF_Free(IntPtr analyzer); - [DllImport("PitchTracker.dll", EntryPoint = "PtAKF_GetNumHalfTones", CallingConvention = CallingConvention.Cdecl)] - public static extern int GetNumHalfTones(); + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern int PtAKF_GetNumHalfTones(); [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void PtAKF_InputByte(IntPtr analyzer, [In] byte[] data, int sampleCt); [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] - private static extern int PtAKF_GetNote(IntPtr analyzer, [Out] out double maxVolume, [Out] float[] weights); + private static extern void PtAKF_SetVolumeThreshold(IntPtr analyzer, float threshold); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern float PtAKF_GetVolumeThreshold(IntPtr analyzer); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern int PtAKF_GetNote(IntPtr analyzer, [Out] out float maxVolume, [Out] float[] weights); #endregion private IntPtr _Instance; @@ -130,41 +124,40 @@ namespace Vocaluxe.Lib.Sound.Record _Instance = PtAKF_Create(step); } - ~CPtAKF() + public override int GetNumHalfTones() { - _Dispose(false); + return PtAKF_GetNumHalfTones(); } - public void Input(byte[] data) + public override float VolumeTreshold + { + get { return PtAKF_GetVolumeThreshold(_Instance); } + set { PtAKF_SetVolumeThreshold(_Instance, value); } + } + + public override void Input(byte[] data) { PtAKF_InputByte(_Instance, data, data.Length / 2); } - public int GetNote(out double maxVolume, float[] weights) + public override int GetNote(out float maxVolume, float[] weights) { return PtAKF_GetNote(_Instance, out maxVolume, weights); } - private void _Dispose(bool disposing) + protected override void _Dispose(bool disposing) { if (_Instance == IntPtr.Zero) throw new ObjectDisposedException(GetType().Name); PtAKF_Free(_Instance); _Instance = IntPtr.Zero; } - - public void Dispose() - { - _Dispose(true); - GC.SuppressFinalize(this); - } } /// /// Pitchtracker (Pt) that uses a dynamic wavelet (DyWa) algorithm - /// Quite fast and perfect in artificial tests, but may fail in real world scenarios (e.g. missing fundamental) /// - class CPtDyWa : IDisposable + class CPtDyWa : CPitchTracker { #region Imports [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] @@ -173,14 +166,17 @@ namespace Vocaluxe.Lib.Sound.Record [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void PtDyWa_Free(IntPtr analyzer); + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern void PtDyWa_SetVolumeThreshold(IntPtr analyzer, float threshold); + + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern float PtDyWa_GetVolumeThreshold(IntPtr analyzer); + [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] private static extern void PtDyWa_InputByte(IntPtr analyzer, [In] byte[] data, int sampleCt); [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] - private static extern int PtDyWa_Process(IntPtr analyzer); - - [DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)] - private static extern double PtDyWa_GetNote(IntPtr analyzer); + private static extern double PtDyWa_FindNote(IntPtr analyzer, [Out] out float maxVolume); #endregion private IntPtr _Instance; @@ -190,38 +186,84 @@ namespace Vocaluxe.Lib.Sound.Record _Instance = PtDyWa_Create(step); } - ~CPtDyWa() - { - _Dispose(false); - } - - public void Input(byte[] data) + public override void Input(byte[] data) { PtDyWa_InputByte(_Instance, data, data.Length / 2); } - public void Process() + public override int GetNote(out float maxVolume, float[] weights) { - PtDyWa_Process(_Instance); + int note = (int)Math.Round(PtDyWa_FindNote(_Instance, out maxVolume)); + _SetWeights(note, weights); + return note; } - public double GetNote() + public override int GetNumHalfTones() { - return PtDyWa_GetNote(_Instance); + return NumHalfTonesDef; } - private void _Dispose(bool disposing) + public override float VolumeTreshold + { + get { return PtDyWa_GetVolumeThreshold(_Instance); } + set { PtDyWa_SetVolumeThreshold(_Instance, value); } + } + + protected override void _Dispose(bool disposing) { if (_Instance == IntPtr.Zero) throw new ObjectDisposedException(GetType().Name); PtDyWa_Free(_Instance); _Instance = IntPtr.Zero; } + } - public void Dispose() + /// + /// Pitchtracker (Pt) in C# that uses IIR filters for smoothing and an intelligent detection algorithm to reduce calculations + /// + class CPtSharp : CPitchTracker + { + private Pitch.PitchTracker _Instance; + + public CPtSharp() { - _Dispose(true); - GC.SuppressFinalize(this); + _Instance = new Pitch.PitchTracker {SampleRate = 44100, DetectLevelThreshold = 0.01f, PitchRecordHistorySize = 1}; + } + + public override void Input(byte[] data) + { + short[] samplesShort = new short[data.Length / 2]; + Buffer.BlockCopy(data, 0, samplesShort, 0, data.Length); + float[] samplesFloat = new float[samplesShort.Length]; + for (int i = 0; i < samplesShort.Length; i++) + samplesFloat[i] = (float)samplesShort[i] / short.MaxValue; + _Instance.ProcessBuffer(samplesFloat); + } + + public override int GetNote(out float maxVolume, float[] weights) + { + int note = _Instance.CurrentPitchRecord.MidiNote - 15 - 21; + maxVolume = _Instance.LastMaxVol; + _SetWeights(note, weights); + return note; + } + + public override int GetNumHalfTones() + { + return NumHalfTonesDef; + } + + public override float VolumeTreshold + { + get { return _Instance.DetectLevelThreshold; } + set { _Instance.DetectLevelThreshold = value; } + } + + protected override void _Dispose(bool disposing) + { + if (_Instance == null) + throw new ObjectDisposedException(GetType().Name); + _Instance = null; } } } \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Record/PitchTracker/IPitchTracker.cs b/Vocaluxe/Lib/Sound/Record/PitchTracker/IPitchTracker.cs new file mode 100644 index 00000000..94c4876f --- /dev/null +++ b/Vocaluxe/Lib/Sound/Record/PitchTracker/IPitchTracker.cs @@ -0,0 +1,73 @@ +using System; +using System.Diagnostics; + +namespace Vocaluxe.Lib.Sound.Record.PitchTracker +{ + /// + /// Base class/Interface for pitch detectors + /// Most likely some structures or system resources are used, hence it should be disposed if no longer needed + /// + abstract class CPitchTracker : IDisposable + { + //Default number of half tones (1 octave) + protected const int NumHalfTonesDef = 12; + + ~CPitchTracker() + { + _Dispose(false); + } + + protected abstract void _Dispose(bool disposing); + + public void Dispose() + { + _Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Method to set weights if the algorithm does not provide anything that could simulate a visualizer. + /// Should be called with each valid detection + /// + /// Current note + /// Array of weights (Length = NumHalfTonesDef) + protected static void _SetWeights(int note, float[] weights) + { + Debug.Assert(weights.Length == NumHalfTonesDef); + for (int i = 0; i < NumHalfTonesDef; i++) + weights[i] *= 0.9f; + if (note < 0) + return; + int w = note % NumHalfTonesDef; + weights[w] = Math.Min(1f, weights[w] + 0.15f); + } + + /// + /// Gets the number of half tones the algorithm provides weights for. + /// This may not be the number of half tones the algorithm detects! + /// + /// + public abstract int GetNumHalfTones(); + + /// + /// The treshold for detecting silence + /// Value (0-1) for which everything below is considered silence + /// + public abstract float VolumeTreshold { get; set; } + + /// + /// Recorded data. Has to be 16 bit short values. + /// + /// 16bit short values of data + public abstract void Input(byte[] data); + + /// + /// Returns the current note detected + /// + /// Current maximum volume (0-1) + /// Array of NumHalfTones floats that gets filled with the current weights (0-1) where each value indicates how strong that tone is. + /// Only for basic visualization as it may not be accurate. + /// Current note index (0 = C2, 12 = C3, ...) + public abstract int GetNote(out float maxVolume, float[] weights); + } +} \ No newline at end of file diff --git a/Vocaluxe/Vocaluxe.csproj b/Vocaluxe/Vocaluxe.csproj index bc2260e4..852cd4f4 100644 --- a/Vocaluxe/Vocaluxe.csproj +++ b/Vocaluxe/Vocaluxe.csproj @@ -223,7 +223,7 @@ - + @@ -268,6 +268,7 @@ +