Improve detection by interpolating and fix test case

This commit is contained in:
Flamefire committed 2014-03-23 12:58:50 +01:00
1 parent dbde93d50c
commit 2d441c3473
1 file changed
+114 -51
+114 -51
View File
@@ -5,7 +5,7 @@ using System.IO;
#if TEST_PITCH #if TEST_PITCH
using System.Diagnostics; using System.Diagnostics;
//using System.Windows.Forms; using System.Windows.Forms;
using VocaluxeLib; using VocaluxeLib;
#endif #endif
@@ -16,8 +16,8 @@ namespace Vocaluxe.Lib.Sound.Record
{ {
public enum EAnalyzeFunction public enum EAnalyzeFunction
{ {
AutoCorrelation, //Faster but strict (All right in set range, more resistent to noise, but does not detect tones outside the range) 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 and more heuristic (false detections in set range but also right detections outside) Amdf //Average magnitude difference, slower, detects everything in range -> HalfToneMax=45
} }
private delegate double DAnalyzeTone(int tone); private delegate double DAnalyzeTone(int tone);
@@ -25,15 +25,18 @@ namespace Vocaluxe.Lib.Sound.Record
//Half tones: C C♯ D Eb E F F♯ G G♯ A Bb B //Half tones: C C♯ D Eb E F F♯ G G♯ A Bb B
private const double _BaseToneFreq = 65.4064; // lowest (half-)tone to analyze (C2 = 65.4064 Hz) 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) private const double _HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
public const int NumHalfTones = 24; //Number of halftone to analyze C2-B4 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 const int _AnalysisBufLen = 4096; private const int _AnalysisBufLen = 4096;
private const int _AnalysisByteBufLen = _AnalysisBufLen * 2; private const int _AnalysisByteBufLen = _AnalysisBufLen * 2;
private readonly Int16[] _AnalysisBuffer = new Int16[_AnalysisBufLen]; private readonly short[] _AnalysisBuffer = new short[_AnalysisBufLen];
private readonly byte[] _AnalysisByteBuffer = new byte[_AnalysisByteBufLen]; //tmp buffer (stream->tmpBuffer->(Int16)AnalysisBuffer) private readonly byte[] _AnalysisByteBuffer = new byte[_AnalysisByteBufLen]; //tmp buffer (stream->tmpBuffer->(Int16)AnalysisBuffer)
private static readonly int[] _SamplesPerPeriodPerTone = new int[NumHalfTones]; //Precalculated table holding sample# per period for each tone //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];
private readonly float[] _TmpWeight = new float[NumHalfTones]; //tmp buffer for weights (gets copied to ToneWeight when checked) private readonly float[] _TmpWeight = new float[NumHalfTones]; //tmp buffer for weights (gets copied to ToneWeight when checked)
// ReSharper disable MemberCanBePrivate.Global // ReSharper disable MemberCanBePrivate.Global
// ReSharper disable ConvertToConstant.Global // ReSharper disable ConvertToConstant.Global
@@ -68,10 +71,10 @@ namespace Vocaluxe.Lib.Sound.Record
static CBuffer() static CBuffer()
{ {
//Init Array to avoid costly calculations //Init Array to avoid costly calculations
for (int toneIndex = 0; toneIndex < NumHalfTones; toneIndex++) for (int toneIndex = 0; toneIndex <= _HalfToneMax; toneIndex++)
{ {
double freq = _BaseToneFreq * Math.Pow(_HalftoneBase, toneIndex); double freq = _BaseToneFreq * Math.Pow(_HalftoneBase, toneIndex);
_SamplesPerPeriodPerTone[toneIndex] = (int)Math.Round(44100.0 / freq); // samples in one period _SamplesPerPeriodPerTone[toneIndex] = 44100.0 / freq; // samples in one period
} }
} }
@@ -161,18 +164,16 @@ namespace Vocaluxe.Lib.Sound.Record
private void _FindMaxVolume() private void _FindMaxVolume()
{ {
short maxVolume = 0; short maxVolume = 0;
for (int i = 0; i < _AnalysisBuffer.Length / 4; i++) for (int i = 0; i < _AnalysisBufLen / 4; i++)
{ {
short volume = _AnalysisBuffer[i]; short volume = _AnalysisBuffer[i];
if (volume < 0) if (volume == Int16.MinValue)
{ {
if (volume == Int16.MinValue) maxVolume = Int16.MaxValue;
{ break;
maxVolume = Int16.MaxValue;
break;
}
volume = (short)-volume;
} }
if (volume < 0)
volume = (short)-volume;
if (volume > maxVolume) if (volume > maxVolume)
maxVolume = volume; maxVolume = volume;
} }
@@ -186,11 +187,12 @@ namespace Vocaluxe.Lib.Sound.Record
double minWeight = 1.0; double minWeight = 1.0;
int maxTone = -1; int maxTone = -1;
// analyze halftones // analyze halftones
// Note: at the lowest tone (~65Hz) and a buffer-size of 4096 // 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 // at 44.1 (or 48kHz) only 6 (or 5) samples are compared, this might be
// too few samples -> use a bigger buffer-size // too few samples -> use a bigger buffer-size
for (int toneIndex = 0; toneIndex < NumHalfTones; toneIndex++) for (int toneIndex = _HalfToneMin; toneIndex <= _HalfToneMax; toneIndex++)
{ {
double curWeight = _AnalyzeToneFunc(toneIndex); double curWeight = _AnalyzeToneFunc(toneIndex);
@@ -203,14 +205,15 @@ namespace Vocaluxe.Lib.Sound.Record
if (curWeight < minWeight) if (curWeight < minWeight)
minWeight = curWeight; minWeight = curWeight;
_TmpWeight[toneIndex] = (float)curWeight; _TmpWeight[toneIndex - _HalfToneMin] = (float)curWeight;
} }
if (maxWeight - minWeight > 0.01) if (maxWeight - minWeight > 0.001)
{ {
Array.Copy(_TmpWeight, ToneWeigth, NumHalfTones); Array.Copy(_TmpWeight, ToneWeigth, NumHalfTones);
ToneAbs = maxTone; ToneAbs = maxTone;
//Console.WriteLine(maxTone);
Tone = maxTone % 12; Tone = maxTone % 12;
ToneValid = true; ToneValid = true;
} }
@@ -220,17 +223,22 @@ namespace Vocaluxe.Lib.Sound.Record
private double _GetAutoCorrelation(int tone) private double _GetAutoCorrelation(int tone)
{ {
int samplesPerPeriod = _SamplesPerPeriodPerTone[tone]; // samples in one period 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 double accumDist = 0; // accumulated distances
// compare correlating samples // compare correlating samples
int sampleIndex = 0; // index of sample to analyze int sampleIndex = 0; // index of sample to analyze
// Start value= index of sample one period ahead // Start value= index of sample one period ahead
for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++) for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex + 1 < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++)
{ {
// calc distance to corresponding sample in next period (lower means more distant) // calc distance to corresponding sample in next period (lower means more distant)
double dist = _AnalysisBuffer[sampleIndex] * _AnalysisBuffer[correlatingSampleIndex]; double dist = _AnalysisBuffer[sampleIndex] *
(_AnalysisBuffer[correlatingSampleIndex] * fLow + _AnalysisBuffer[correlatingSampleIndex + 1] * fHigh);
//double dist = _AnalysisBuffer[sampleIndex] * _AnalysisBuffer[correlatingSampleIndex];
accumDist += dist; accumDist += dist;
} }
@@ -241,17 +249,22 @@ namespace Vocaluxe.Lib.Sound.Record
private double _GetAmdf(int tone) private double _GetAmdf(int tone)
{ {
int samplesPerPeriod = _SamplesPerPeriodPerTone[tone]; // samples in one period double samplesPerPeriodD = _SamplesPerPeriodPerTone[tone]; // samples in one period
int samplesPerPeriod = (int)samplesPerPeriodD;
double fHigh = samplesPerPeriodD - samplesPerPeriod;
double fLow = 1.0 - fHigh;
int accumDist = 0; // accumulated distances double accumDist = 0; // accumulated distances
// compare correlating samples // compare correlating samples
int sampleIndex = 0; // index of sample to analyze int sampleIndex = 0; // index of sample to analyze
// Start value= index of sample one period ahead // Start value= index of sample one period ahead
for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++) 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) // calc distance (correlation: 1-dist/IntMax*2) to corresponding sample in next period (0=equal .. IntMax*2=totally different)
int dist = Math.Abs(_AnalysisBuffer[sampleIndex] - _AnalysisBuffer[correlatingSampleIndex]); //int dist = (int)Math.Abs(_AnalysisBuffer[sampleIndex] - _AnalysisBuffer[correlatingSampleIndex]);
double dist = Math.Abs(_AnalysisBuffer[sampleIndex] -
(_AnalysisBuffer[correlatingSampleIndex] * fLow + _AnalysisBuffer[correlatingSampleIndex + 1] * fHigh));
accumDist += dist; accumDist += dist;
} }
//Use analyzed sample count here. BufLen yields more errors //Use analyzed sample count here. BufLen yields more errors
@@ -273,10 +286,11 @@ namespace Vocaluxe.Lib.Sound.Record
private static string _ToneToNote(int tone, bool withOctave = true) private static string _ToneToNote(int tone, bool withOctave = true)
{ {
tone += 24;
string[] notes = {"C", "C♯", "D", "Eb", "E", "F", "F♯", "G", "G♯", "A", "Bb", "B"}; string[] notes = {"C", "C♯", "D", "Eb", "E", "F", "F♯", "G", "G♯", "A", "Bb", "B"};
string result = notes[tone % 12]; string result = notes[tone % 12];
if (withOctave) if (withOctave)
result += (tone / 12) + 2; result += (tone / 12);
return result; return result;
} }
@@ -285,18 +299,17 @@ namespace Vocaluxe.Lib.Sound.Record
if (_PitchTestRun) if (_PitchTestRun)
return; return;
_PitchTestRun = true; _PitchTestRun = true;
const int toneFrom = 0; int toneFrom = Math.Max(0, _HalfToneMin);
const int tontTo = 48; const int toneTo = 47; //B5
Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(tontTo)); Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(toneTo));
byte[] data; byte[] data;
int ok = 0; int ok = 0;
for (int distort = 0; distort < 5; distort++) for (int distort = 0; distort < 10; distort++)
{ {
for (int tone = toneFrom; tone <= tontTo; tone++) for (int tone = toneFrom; tone <= toneTo; tone++)
{ {
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone), 44100, out data); _GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone), 44100, out data);
for (int i = 0; i < distort; i++) _Distort(data, tone, distort);
_Distort(data, tone + 2 + i * 2);
ProcessNewBuffer(data); ProcessNewBuffer(data);
AnalyzeBuffer(); AnalyzeBuffer();
@@ -313,40 +326,90 @@ namespace Vocaluxe.Lib.Sound.Record
AnalyzeBuffer(); AnalyzeBuffer();
Stopwatch sw = new Stopwatch(); Stopwatch sw = new Stopwatch();
sw.Start(); sw.Start();
for (int i = 0; i < 10000; i++) const int repeats = 2000;
for (int i = 0; i < repeats; i++)
{ {
ProcessNewBuffer(data); //ProcessNewBuffer(data);
AnalyzeBuffer(); //AnalyzeBuffer();
// _FindMaxVolume(); _FindMaxVolume();
// _AnalyzeTones(); _AnalyzeTones();
} }
sw.Stop(); sw.Stop();
string msg = "Analyser: Errors: " + ((tontTo - toneFrom + 1) * 5 - ok) + "; Speed: " + string msg = "Analyser: Errors: " + ((toneTo - toneFrom + 1) * 10 - ok) + "; Speed: " +
(int)(10000.0 / (sw.ElapsedMilliseconds / 1000.0)) + " buffers/s"; (int)(repeats / (sw.ElapsedMilliseconds / 1000.0)) + " buffers/s";
Console.WriteLine(msg); Console.WriteLine(msg);
//MessageBox.Show(msg); MessageBox.Show(msg);
Reset(); Reset();
} }
private static void _Distort(byte[] data, int tone) private static short[] _GetDistort(int len, int tone, int type)
{ {
short[] sdata = new short[data.Length / 2]; short[] sdata = new short[len];
Buffer.BlockCopy(data, 0, sdata, 0, data.Length); if (type < 9)
short[] sdata2 = new short[data.Length / 2];
if (tone < 4)
{ {
int newTone = 1;
switch (type)
{
case 0:
newTone = 2;
break;
case 1:
newTone = 3;
break;
case 2:
newTone = 6;
break;
case 3:
newTone = 8;
break;
case 4:
newTone = 10;
break;
case 5:
newTone = 15;
break;
case 6:
newTone = 25;
break;
case 7:
newTone = 26;
break;
case 8:
newTone = 29;
break;
}
byte[] data2; byte[] data2;
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone), 44100, out data2); _GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone + newTone), 44100, out data2);
Buffer.BlockCopy(data2, 0, sdata2, 0, data.Length); Buffer.BlockCopy(data2, 0, sdata, 0, len * 2);
} }
else else
{ {
Random r = new Random(0xBEEF); Random r = new Random(0xBEEF);
for (int i = 0; i < sdata.Length; i++) for (int i = 0; i < sdata.Length; i++)
sdata2[i] = (short)(r.Next() - Int16.MinValue); sdata[i] = (short)(r.Next() - Int16.MinValue);
} }
return sdata;
}
private static void _Distort(byte[] data, int tone, int distortCt)
{
if (distortCt < 1)
return;
short[] sdata = new short[data.Length / 2];
Buffer.BlockCopy(data, 0, sdata, 0, data.Length);
short[][] distortions = new short[distortCt][];
for (int i = 0; i < distortCt; i++)
distortions[i] = _GetDistort(data.Length / 2, tone, i);
for (int i = 0; i < sdata.Length; i++) for (int i = 0; i < sdata.Length; i++)
sdata[i] = (short)(sdata[i] * 4.0 / 5.0 + sdata2[i] / 5.0); {
double distortion = 0;
for (int j = 0; j < distortCt; j++)
distortion += distortions[j][i];
distortion /= distortCt;
sdata[i] = (short)(sdata[i] * 4.0 / 5.0 + distortion / 5.0);
}
Buffer.BlockCopy(sdata, 0, data, 0, data.Length); Buffer.BlockCopy(sdata, 0, data, 0, data.Length);
} }