Use window function
Update reference notes Improve comparison
This commit is contained in:
1 parent
7c380f62f1
commit
be6a40aa1d
4 files changed
+108
-73
No files matched your search
Binary file not shown.
+82
-58
@@ -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<int>(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<int>(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<int>(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;
|
||||
}
|
||||
/*
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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<STimedNote> 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<STimedNote> 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) +
|
||||
|
||||
Reference in new issue
Block a user