Add auto-correlation function for sound detection

Update test-code to check also mixed sounds
This commit is contained in:
Flamefire committed 2014-03-23 12:58:40 +01:00
1 parent 15e08ca831
commit dbde93d50c
1 file changed
+103 -24
+103 -24
View File
@@ -5,6 +5,7 @@ using System.IO;
#if TEST_PITCH #if TEST_PITCH
using System.Diagnostics; using System.Diagnostics;
//using System.Windows.Forms;
using VocaluxeLib; using VocaluxeLib;
#endif #endif
@@ -13,10 +14,18 @@ namespace Vocaluxe.Lib.Sound.Record
{ {
class CBuffer : IDisposable class CBuffer : IDisposable
{ {
public enum EAnalyzeFunction
{
AutoCorrelation, //Faster but strict (All right in set range, more resistent to noise, but does not detect tones outside the range)
Amdf //Average magnitude difference, slower and more heuristic (false detections in set range but also right detections outside)
}
private delegate double DAnalyzeTone(int tone);
//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 = 17; //Number of halftone to analyze C2-E3 (TODO: use more/full octaves?) public const int NumHalfTones = 24; //Number of halftone to analyze C2-B4
private const int _AnalysisBufLen = 4096; private const int _AnalysisBufLen = 4096;
private const int _AnalysisByteBufLen = _AnalysisBufLen * 2; private const int _AnalysisByteBufLen = _AnalysisBufLen * 2;
@@ -26,6 +35,16 @@ namespace Vocaluxe.Lib.Sound.Record
private static readonly int[] _SamplesPerPeriodPerTone = new int[NumHalfTones]; //Precalculated table holding sample# per period for each tone private static readonly int[] _SamplesPerPeriodPerTone = new int[NumHalfTones]; //Precalculated table holding sample# per period for each tone
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 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;
// ReSharper restore FieldCanBeMadeReadOnly.Local
private double _MaxVolume; private double _MaxVolume;
private bool _NewSamples; private bool _NewSamples;
@@ -37,6 +56,10 @@ namespace Vocaluxe.Lib.Sound.Record
MinVolume = 0.02f; MinVolume = 0.02f;
ToneWeigth = new float[NumHalfTones]; ToneWeigth = new float[NumHalfTones];
Reset(); Reset();
if (AnalyzeFunction == EAnalyzeFunction.Amdf)
_AnalyzeToneFunc = _GetAmdf;
else if (AnalyzeFunction == EAnalyzeFunction.AutoCorrelation)
_AnalyzeToneFunc = _GetAutoCorrelation;
#if TEST_PITCH #if TEST_PITCH
_TestPitchDetection(); _TestPitchDetection();
#endif #endif
@@ -128,7 +151,7 @@ namespace Vocaluxe.Lib.Sound.Record
_FindMaxVolume(); _FindMaxVolume();
if (MaxVolume >= MinVolume) if (MaxVolume >= MinVolume)
_AnalyzeByAutocorrelation(); _AnalyzeTones();
else else
ToneValid = false; ToneValid = false;
} }
@@ -156,7 +179,7 @@ namespace Vocaluxe.Lib.Sound.Record
_MaxVolume = (double)maxVolume / Int16.MaxValue; _MaxVolume = (double)maxVolume / Int16.MaxValue;
} }
private void _AnalyzeByAutocorrelation() private void _AnalyzeTones()
{ {
// prepare to analyze // prepare to analyze
double maxWeight = -1.0; double maxWeight = -1.0;
@@ -169,7 +192,7 @@ namespace Vocaluxe.Lib.Sound.Record
// 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 = 0; toneIndex < NumHalfTones; toneIndex++)
{ {
double curWeight = _AnalyzeAutocorrelationTone(toneIndex); double curWeight = _AnalyzeToneFunc(toneIndex);
if (curWeight > maxWeight) if (curWeight > maxWeight)
{ {
@@ -195,7 +218,28 @@ namespace Vocaluxe.Lib.Sound.Record
ToneValid = false; ToneValid = false;
} }
private double _AnalyzeAutocorrelationTone(int tone) private double _GetAutoCorrelation(int tone)
{
int samplesPerPeriod = _SamplesPerPeriodPerTone[tone]; // samples in one period
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 < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++)
{
// calc distance to corresponding sample in next period (lower means more distant)
double dist = _AnalysisBuffer[sampleIndex] * _AnalysisBuffer[correlatingSampleIndex];
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)
{ {
int samplesPerPeriod = _SamplesPerPeriodPerTone[tone]; // samples in one period int samplesPerPeriod = _SamplesPerPeriodPerTone[tone]; // samples in one period
@@ -206,12 +250,11 @@ namespace Vocaluxe.Lib.Sound.Record
// 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 < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++)
{ {
// calc distance (correlation: 1-dist/IntMax) to corresponding sample in next period // calc distance (correlation: 1-dist/IntMax*2) to corresponding sample in next period (0=equal .. IntMax*2=totally different)
// distance (0=equal .. IntMax=totally different) between correlated samples
int dist = Math.Abs(_AnalysisBuffer[sampleIndex] - _AnalysisBuffer[correlatingSampleIndex]); int dist = Math.Abs(_AnalysisBuffer[sampleIndex] - _AnalysisBuffer[correlatingSampleIndex]);
accumDist += dist; accumDist += dist;
} }
//Use analyzed sample count here. BufLen yields more errors
return 1.0 - (double)accumDist / Int16.MaxValue / sampleIndex; return 1.0 - (double)accumDist / Int16.MaxValue / sampleIndex;
} }
@@ -228,7 +271,7 @@ namespace Vocaluxe.Lib.Sound.Record
#if TEST_PITCH #if TEST_PITCH
private static bool _PitchTestRun; private static bool _PitchTestRun;
private string _ToneToNote(int tone, bool withOctave = true) private static string _ToneToNote(int tone, bool withOctave = true)
{ {
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];
@@ -243,35 +286,71 @@ namespace Vocaluxe.Lib.Sound.Record
return; return;
_PitchTestRun = true; _PitchTestRun = true;
const int toneFrom = 0; const int toneFrom = 0;
const int tontTo = 47; const int tontTo = 48;
Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(tontTo)); Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(tontTo));
byte[] data; byte[] data;
for (int tone = toneFrom; tone <= tontTo; tone++) int ok = 0;
for (int distort = 0; distort < 5; distort++)
{ {
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone), 44100, out data); for (int tone = toneFrom; tone <= tontTo; tone++)
ProcessNewBuffer(data); {
AnalyzeBuffer(); _GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone), 44100, out data);
if (!ToneValid) for (int i = 0; i < distort; i++)
CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " not detected"); _Distort(data, tone + 2 + i * 2);
else if (Tone != tone % 12)
CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " wrongly detected as " + _ToneToNote(Tone, false)); ProcessNewBuffer(data);
AnalyzeBuffer();
if (!ToneValid)
CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " not detected (Distortion: " + distort + ")");
else if (Tone != tone % 12)
CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " wrongly detected as " + _ToneToNote(Tone, false) + " (Distortion: " + distort + ")");
else
ok++;
}
} }
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, 5), 44100, out data); _GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, 5), 44100, out data);
ProcessNewBuffer(data);
AnalyzeBuffer();
Stopwatch sw = new Stopwatch(); Stopwatch sw = new Stopwatch();
sw.Start(); sw.Start();
for (int i = 0; i < 10000; i++) for (int i = 0; i < 10000; i++)
{ {
// ProcessNewBuffer(data); ProcessNewBuffer(data);
// AnalyzeBuffer(); AnalyzeBuffer();
_FindMaxVolume(); // _FindMaxVolume();
_AnalyzeByAutocorrelation(); // _AnalyzeTones();
} }
sw.Stop(); sw.Stop();
Console.WriteLine("Analysing took " + sw.ElapsedMilliseconds + "ms"); string msg = "Analyser: Errors: " + ((tontTo - toneFrom + 1) * 5 - ok) + "; Speed: " +
(int)(10000.0 / (sw.ElapsedMilliseconds / 1000.0)) + " buffers/s";
Console.WriteLine(msg);
//MessageBox.Show(msg);
Reset(); Reset();
} }
private void _GetSineWave(double freq, int sampleRate, out byte[] data) private static void _Distort(byte[] data, int tone)
{
short[] sdata = new short[data.Length / 2];
Buffer.BlockCopy(data, 0, sdata, 0, data.Length);
short[] sdata2 = new short[data.Length / 2];
if (tone < 4)
{
byte[] data2;
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone), 44100, out data2);
Buffer.BlockCopy(data2, 0, sdata2, 0, data.Length);
}
else
{
Random r = new Random(0xBEEF);
for (int i = 0; i < sdata.Length; i++)
sdata2[i] = (short)(r.Next() - Int16.MinValue);
}
for (int i = 0; i < sdata.Length; i++)
sdata[i] = (short)(sdata[i] * 4.0 / 5.0 + sdata2[i] / 5.0);
Buffer.BlockCopy(sdata, 0, data, 0, data.Length);
}
private static void _GetSineWave(double freq, int sampleRate, out byte[] data)
{ {
const short max = short.MaxValue; const short max = short.MaxValue;
const int len = _AnalysisBufLen; //sampleRate * durationMs / 1000; const int len = _AnalysisBufLen; //sampleRate * durationMs / 1000;