Add auto-correlation function for sound detection
Update test-code to check also mixed sounds
This commit is contained in:
1 parent
15e08ca831
commit
dbde93d50c
1 file changed
+103
-24
@@ -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;
|
||||||
|
|||||||
Reference in new issue
Block a user