diff --git a/PitchTracker/TestSounds.rar b/PitchTracker/TestSounds.rar index 463321f4..7dd70d57 100644 Binary files a/PitchTracker/TestSounds.rar and b/PitchTracker/TestSounds.rar differ diff --git a/PitchTracker/ptAKF.cpp b/PitchTracker/ptAKF.cpp index be84893b..f5f95bf7 100644 --- a/PitchTracker/ptAKF.cpp +++ b/PitchTracker/ptAKF.cpp @@ -108,24 +108,26 @@ int PtAKF::_GetNote(float* restrict samples, double* restrict maxVolume, float* //maxVolumeL /= 32767.; *maxVolume = maxVolumeL; + float samplesWindowed[_SampleCt]; + for(int i = 0; i < _SampleCt; i++){ - samples[i] *= _Window[i]; + samplesWindowed[i] = samples[i] * _Window[i]; } // Now analyze the samples and get peaks at the most appropriate tones - SPeak peaks[_MaxPeaks]; - InitPeaks(peaks); - //Attention: We have a peak at lag 0 that might stretch that far, that we detect a wrong "peak" at _MaxHalfTone //Because of that we filter out all tones that are past the last zero crossing from below but keep tones with decreasing weights (going towards zero crossing from above) int lastValidTone = 0; float lastWeight = 1.f; + float maxWeight = 0.f; for (int toneIndex = _MinHalfTone; toneIndex <= _MaxHalfTone; toneIndex++) { - float curWeight = _AnalyzeByTone(samples, toneIndex); + float curWeight = _AnalyzeByTone(samples, samplesWindowed, toneIndex); - AddPeak(peaks, curWeight, toneIndex); + if(curWeight > maxWeight){ + maxWeight = curWeight; + } weights[toneIndex - _MinHalfTone] = curWeight; @@ -137,7 +139,7 @@ int PtAKF::_GetNote(float* restrict samples, double* restrict maxVolume, float* // Now clear off the lag 0 peak if required - if(peaks[_MaxPeaks - 1].toneIndex ==_MaxHalfTone){ + if(maxWeight >= weights[_MaxHalfTone]){ //We might have caught the lag 0 peak so go a bit further to check for other zero crossings (or we won't be able to detect _MaxHalfTone) float lastWeight = _AKFByTone(samples, _MaxHalfTone); for(int toneIndex = _MaxHalfTone+1; toneIndex <= _MaxHalfTone + _HalfTonesAdd; toneIndex++){ @@ -148,40 +150,57 @@ int PtAKF::_GetNote(float* restrict samples, double* restrict maxVolume, float* } lastWeight = curWeight; } - if(lastValidTone < peaks[_MaxPeaks - 1].toneIndex){ - InitPeaks(peaks); - for(int toneIndex = _MinHalfTone; toneIndex <= lastValidTone; toneIndex++){ - AddPeak(peaks, weights[toneIndex - _MinHalfTone], toneIndex); - } - //Set all invalid weights to 0 - for(int toneIndex = lastValidTone + 1;toneIndex < _MaxHalfTone; toneIndex++){ - weights[toneIndex - _MinHalfTone] = 0.f; - } + //Set all invalid weights to 0 + for(int toneIndex = lastValidTone + 1;toneIndex < _MaxHalfTone; toneIndex++){ + weights[toneIndex - _MinHalfTone] = 0.f; } } + SPeak peaks[_MaxPeaks]; + InitPeaks(peaks); + int numPeaks = 0; + bool up = true; + for(int i = _MinHalfTone + 1; i <= _MaxHalfTone; i++){ + if(weights[i-1] > weights[i]){ + if(up){ + AddPeak(peaks, weights[i - 1 - _MinHalfTone], i - 1); + numPeaks++; + up = false; + } + }else if(!up){ + up = true; + } + } + if(up){ + AddPeak(peaks, weights[_MaxHalfTone - _MinHalfTone], _MaxHalfTone); + numPeaks++; + } + if(numPeaks > _MaxPeaks) + numPeaks = _MaxPeaks; + // Further analyze the found peaks and find the real maximum // This is necessary because we may have our real peak a bit off the exact tone frequency // and a 'wrong' peak that is exactly at another tone which might become higher than the one at the 'right' tone - int maxTone = peaks[_MaxPeaks - 1].toneIndex; + int maxTone = peaks[_MaxPeaks-1].toneIndex; int maxToneFine = -1; - float maxWeight = peaks[_MaxPeaks - 1].weight; - for(int i = 0; i < _MaxPeaks; i++){ + maxWeight = peaks[_MaxPeaks-1].weight; + for(int i = _MaxPeaks - numPeaks; i < _MaxPeaks; i++){ + float curWeight = peaks[i].weight; + if(curWeight * 3.f < maxWeight) + continue; int toneIndex = peaks[i].toneIndex; - float curWeightCenter = peaks[i].weight; - float curWeightDown = _AnalyzeBySampleCt(samples, _SamplesPerPeriodPerToneFine[toneIndex * 2]); - float curWeight; + float curWeightDown = _AnalyzeBySampleCt(samples, samplesWindowed, _SamplesPerPeriodPerToneFine[toneIndex * 2]); int otherToneIndex; int otherToneFineIndex; - if(curWeightDown > curWeightCenter){ + if(curWeightDown > curWeight){ otherToneIndex = toneIndex - 1; otherToneFineIndex = 1; curWeight = curWeightDown; }else{ - float curWeightUp = _AnalyzeBySampleCt(samples, _SamplesPerPeriodPerToneFine[toneIndex * 2 + 1]); - if(curWeightUp > curWeightCenter){ - otherToneIndex = (toneIndex < _MaxHalfTone)? toneIndex + 1: -1; // If the other tone is invalid just set it to -1 which will be skipped below + float curWeightUp = _AnalyzeBySampleCt(samples, samplesWindowed, _SamplesPerPeriodPerToneFine[toneIndex * 2 + 1]); + if(curWeightUp > curWeight){ + otherToneIndex = (toneIndex < _MaxHalfTone) ? toneIndex + 1: -1; // If the other tone is invalid just set it to -1 which will be skipped below otherToneFineIndex = 0; curWeight = curWeightUp; }else @@ -196,7 +215,7 @@ int PtAKF::_GetNote(float* restrict samples, double* restrict maxVolume, float* // Now check also neighbouring tone if(otherToneIndex > 0){ - float curWeightOther = _AnalyzeBySampleCt(samples, _SamplesPerPeriodPerToneFine[otherToneIndex * 2 + otherToneFineIndex]); + float curWeightOther = _AnalyzeBySampleCt(samples, samplesWindowed, _SamplesPerPeriodPerToneFine[otherToneIndex * 2 + otherToneFineIndex]); if(curWeightOther > weights[otherToneIndex - _MinHalfTone]){ weights[otherToneIndex - _MinHalfTone] = curWeightOther; if(curWeightOther > maxWeight){ @@ -209,45 +228,26 @@ int PtAKF::_GetNote(float* restrict samples, double* restrict maxVolume, float* } float energy = _AKFBySampleCt(samples, 0); - float maxAKF = (maxToneFine < 0) ? _AKFByTone(samples, maxTone):_AKFBySampleCt(samples, _SamplesPerPeriodPerToneFine[maxTone * 2 + maxToneFine]); - float aa = _AKFByTone(samples, maxTone); + float maxAKF = (maxToneFine < 0) ? _AKFByTone(samples, maxTone) : _AKFBySampleCt(samples, _SamplesPerPeriodPerToneFine[maxTone * 2 + maxToneFine]); //if(maxWeight - minWeight > 0.025){ - if(maxAKF >= 0.4 * energy){ + if(maxAKF >= 0.35f * energy){ return maxTone; }else return -1; } -float PtAKF::_AnalyzeByTone(float* restrict samples, int toneIndex){ - return _AnalyzeBySampleCt(samples, _SamplesPerPeriodPerTone[toneIndex]); +float PtAKF::_AnalyzeByTone(float* restrict samples, float* restrict samplesWindowed, int toneIndex){ + return _AnalyzeBySampleCt(samples, samplesWindowed, _SamplesPerPeriodPerTone[toneIndex]); } -float PtAKF::_AnalyzeBySampleCt(float* restrict samples, float samplesPerPeriodD){ +float PtAKF::_AnalyzeBySampleCt(float* restrict samples, float* restrict samplesWindowed, float samplesPerPeriodD){ // Use method by Kobayashi and Shimamura (2001): Combine AKF and AMDF to a new f(z)=AKF(z)/(AMDF(z)+k) with k=1 - int samplesPerPeriod = static_cast(samplesPerPeriodD); - float fHigh = samplesPerPeriodD - samplesPerPeriod; - float fLow = 1.0f - fHigh; - float accumDistAKF = 0; // accumulated distances - float accumDistAMDF = 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 - float xn = samples[sampleIndex]; - float xnt = samples[correlatingSampleIndex] * fLow + samples[correlatingSampleIndex + 1] * fHigh; - accumDistAKF += xn * xnt; - accumDistAMDF += abs(xn - xnt); - } - - accumDistAKF /= _SampleCt; - accumDistAMDF /= sampleIndex; + float akf = _AKFBySampleCt(samplesWindowed, samplesPerPeriodD); + float amdf = _AMDFBySampleCt(samples, samplesPerPeriodD); //return accumDistAKF / (accumDistAMDF * 32767.f + 32767.f * 32767.f); // Need to scale AKF by MAX^2 and AMDF by MAX, so do some maths to divide only once - float result = accumDistAKF / (accumDistAMDF + 1.f); + float result = akf / (amdf + 1.f); return result; // Need to scale AKF by MAX^2 and AMDF by MAX, so do some maths to divide only once //{toneIndex}: {accumDistAKF} ; {accumDistAMDF}; {result} } @@ -257,12 +257,11 @@ float PtAKF::_AKFByTone(float* restrict samples, int toneIndex){ } float PtAKF::_AKFBySampleCt(float* restrict samples, float samplesPerPeriodD){ - // Do a fast, non-scaled AKF (used e.g. for Lag 0 peak removal) int samplesPerPeriod = static_cast(samplesPerPeriodD); float fHigh = samplesPerPeriodD - samplesPerPeriod; float fLow = 1.0f - fHigh; - float accumDistAKF = 0; // accumulated distances + float accumDist = 0; // accumulated distances // compare correlating samples int sampleIndex = 0; // index of sample to analyze @@ -272,10 +271,35 @@ float PtAKF::_AKFBySampleCt(float* restrict samples, float samplesPerPeriodD){ // calc distance to corresponding sample in next period float xn = samples[sampleIndex]; float xnt = samples[correlatingSampleIndex] * fLow + samples[correlatingSampleIndex + 1] * fHigh; - accumDistAKF += xn * xnt; + accumDist += xn * xnt; } - return accumDistAKF; + return accumDist / _SampleCt; +} + +float PtAKF::_AMDFByTone(float* restrict samples, int toneIndex){ + return _AKFBySampleCt(samples, _SamplesPerPeriodPerTone[toneIndex]); +} + +float PtAKF::_AMDFBySampleCt(float* restrict samples, float samplesPerPeriodD){ + int samplesPerPeriod = static_cast(samplesPerPeriodD); + float fHigh = samplesPerPeriodD - samplesPerPeriod; + float fLow = 1.0f - fHigh; + + float 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 + float xn = samples[sampleIndex]; + float xnt = samples[correlatingSampleIndex] * fLow + samples[correlatingSampleIndex + 1] * fHigh; + accumDist += abs(xn - xnt); + } + + return accumDist / sampleIndex; } /* diff --git a/PitchTracker/ptAKF.h b/PitchTracker/ptAKF.h index 688e04d0..02a2baf2 100644 --- a/PitchTracker/ptAKF.h +++ b/PitchTracker/ptAKF.h @@ -41,19 +41,21 @@ private: static float* restrict _SamplesPerPeriodPerToneFine; static float* restrict _Window; static constexpr int _MinHalfTone = 0; //C2 - static constexpr int _MaxHalfTone = 55;//47; //B5 + static constexpr int _MaxHalfTone = 56;//47; //B5 static constexpr int _HalfTonesAdd = 4; //Additonal half tones to analyze to remove the peak at lag 0 - static constexpr int _MaxPeaks = 3; + static constexpr int _MaxPeaks = 10; constexpr static size_t _SampleCt = 2048; RingBuffer<_SampleCt * 2> _AnalysisBuf; unsigned _Step; static int _GetNote(float* restrict samples, double* restrict maxVolume, float* restrict weights); - static float _AnalyzeBySampleCt(float* restrict samples, float samplesPerPeriodD); - static inline float _AnalyzeByTone(float* restrict samples, int toneIndex); + 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); static inline float _AKFByTone(float* restrict samples, int toneIndex); + static float _AMDFBySampleCt(float* restrict samples, float samplesPerPeriodD); + static inline float _AMDFByTone(float* restrict samples, int toneIndex); static inline void InitPeaks(SPeak peaks[_MaxPeaks]); static inline void AddPeak(SPeak peaks[_MaxPeaks], float curWeight, int toneIndex); }; \ No newline at end of file diff --git a/Vocaluxe/Lib/Sound/Record/CBuffer.cs b/Vocaluxe/Lib/Sound/Record/CBuffer.cs index 2479c76a..df6284a3 100644 --- a/Vocaluxe/Lib/Sound/Record/CBuffer.cs +++ b/Vocaluxe/Lib/Sound/Record/CBuffer.cs @@ -193,8 +193,8 @@ namespace Vocaluxe.Lib.Sound.Record if (_TestFile("toneG4.wav", 31)) ok++; tests++;*/ - _TestFile("sClausVoc.wav", "sClausVoc.txt", ref tests, ref ok); _TestFile("whistling3.wav", "whistling3.txt", ref tests, ref ok); + _TestFile("sClausVoc.wav", "sClausVoc.txt", ref tests, ref ok); _GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, 5), 44100, ref angle, out data); Stopwatch sw = new Stopwatch(); @@ -255,6 +255,22 @@ namespace Vocaluxe.Lib.Sound.Record return ok >= ct - 4; } + private bool _IsNoteValid(int note, int time, IList tones) + { + const int lastNoteMaxTimeDiff = 1024 * 1000 / 44100; // old note is valid for 1024 more samples + for (int i = 0; i < tones.Count; i++) + { + if (tones[i].Time > time) + break; + if (i + 1 == tones.Count || time <= tones[i + 1].Time + lastNoteMaxTimeDiff) + { + if (tones[i].Note == note) + return true; + } + } + return false; + } + private void _TestFile(string fileName, IList tones, ref int ct, ref int ok) { CWavFile wavFile = new CWavFile(); @@ -270,9 +286,6 @@ namespace Vocaluxe.Lib.Sound.Record int samplesRead = 0; int curTimeIndex = -1; int curNote = -1; - int lastNote = -1; //It is ok not to detect a note change immediately so the old note is kept for a bit as ok - int lastNoteEndTime = 0; - const int lastNoteMaxTimeDiff = 1024 * 1000 / 44100; // old note is valid for 1024 more samples const int maxSamplesPerBatch = 512; CAnalyzer analyzer2 = new CAnalyzer(); CPtDyWa analyzer3 = new CPtDyWa(); @@ -290,14 +303,10 @@ namespace Vocaluxe.Lib.Sound.Record while (curTimeIndex + 1 < tones.Count && time >= tones[curTimeIndex + 1].Time) { curTimeIndex++; - lastNote = curNote; - lastNoteEndTime = tones[curTimeIndex].Time; curNote = tones[curTimeIndex].Note; } if (curNote < 0) continue; - if (lastNoteEndTime + lastNoteMaxTimeDiff < time) - lastNote = curNote; int tone1 = ToneValid ? ToneAbs : -1; int tone2 = (int)Math.Round(analyzer2.FindNote(64, 1770)); int tone3 = (int)Math.Round(analyzer3.GetNote()); @@ -308,9 +317,9 @@ namespace Vocaluxe.Lib.Sound.Record CBase.Log.LogDebug("Note " + _ToneToNote(curNote) + " at " + time + "ms wrongly detected as " + _ToneToNote(ToneAbs)); else ok++;*/ - bool ok1 = tone1 == curNote || tone1 == lastNote; - bool ok2 = tone2 == curNote || tone2 == lastNote; - bool ok3 = tone3 == curNote || tone3 == lastNote; + bool ok1 = _IsNoteValid(tone1, time, tones); + bool ok2 = _IsNoteValid(tone2, time, tones); + bool ok3 = _IsNoteValid(tone3, time, tones); if (!ok1 || !ok2 || !ok3) { CBase.Log.LogDebug("Note " + _ToneToNote(curNote) + " at " + time + "ms detected as " + _ToneToNote(tone1) + (ok1 ? "" : "(!)") + "; " + _ToneToNote(tone2) +