Optimize tone detection (40% faster now)

This commit is contained in:
Flamefire committed 2014-03-12 18:42:45 +01:00
1 parent 26564874e9
commit 45e480785d
3 files changed
+89 -148

No files matched your search

+87 -146
View File
@@ -205,26 +205,21 @@ namespace Vocaluxe.Base
#region Record #region Record
private static IRecord _Record; private static IRecord _Record;
public static bool RecordInit() public static void RecordInit()
{ {
switch (CConfig.RecordLib) switch (CConfig.RecordLib)
{ {
case ERecordLib.PortAudio:
_Record = new CPortAudioRecord();
break;
#if WIN #if WIN
case ERecordLib.DirectSound: case ERecordLib.DirectSound:
_Record = new CDirectSoundRecord(); _Record = new CDirectSoundRecord();
break; break;
#endif #endif
// case ERecordLib.PortAudio:
default: default:
_Record = new CPortAudioRecord(); _Record = new CPortAudioRecord();
break; break;
} }
return true;
} }
public static void RecordCloseAll() public static void RecordCloseAll()
@@ -317,117 +312,66 @@ namespace Vocaluxe.Base
class CBuffer : IDisposable class CBuffer : IDisposable
{ {
private const double _BaseToneFreq = 65.4064; private const double _BaseToneFreq = 65.4064;
private const int _NumHalfTones = 47; public const int NumHalfTones = 17;
private readonly float[] _ToneWeigth; private const int _AnalysisBufLen = 4096;
private Int16[] _AnalysisBuffer = new Int16[4096]; private const int _AnalysisByteBufLen = _AnalysisBufLen * 2;
private readonly Object _AnalysisBufferLock = new Object();
private readonly Int16[] _AnalysisBuffer = new Int16[_AnalysisBufLen];
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
private readonly float[] _TmpWeight = new float[NumHalfTones]; //tmp buffer for weights (gets copied to ToneWeight when checked)
private bool _ToneValid;
private int _Tone;
private int _ToneAbs;
private double _MaxVolume; private double _MaxVolume;
private bool _NewSamples; private bool _NewSamples;
private MemoryStream _Stream; // full buffer private MemoryStream _Stream = new MemoryStream(); // full buffer
public CBuffer() public CBuffer()
{ {
_ToneWeigth = new float[_NumHalfTones]; ToneWeigth = new float[NumHalfTones];
for (int i = 0; i < _ToneWeigth.Length; i++) for (int i = 0; i < ToneWeigth.Length; i++)
_ToneWeigth[i] = 0.99f; ToneWeigth[i] = 0.99f;
_Stream = new MemoryStream();
_NewSamples = false;
} }
public int NumHalfTones static CBuffer()
{ {
get { return _NumHalfTones; } const double halftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
} //Init Array to avoid costly calculations
for (int toneIndex = 0; toneIndex < NumHalfTones; toneIndex++)
public int ToneAbs
{
get
{ {
lock (_AnalysisBufferLock) double freq = _BaseToneFreq * Math.Pow(halftoneBase, toneIndex);
{ _SamplesPerPeriodPerTone[toneIndex] = (int)Math.Round(44100.0 / freq); // samples in one period
return _ToneAbs;
}
} }
} }
public int Tone public int ToneAbs { get; private set; }
{
get public int Tone { get; set; }
{
lock (_AnalysisBufferLock)
{
return _Tone;
}
}
set
{
lock (_AnalysisBufferLock)
{
_Tone = value;
}
}
}
public float MaxVolume public float MaxVolume
{ {
get get { return (float)_MaxVolume; }
{
lock (_AnalysisBufferLock)
{
return (float)_MaxVolume;
}
}
} }
public bool ToneValid public bool ToneValid { get; private set; }
{
get
{
lock (_AnalysisBufferLock)
{
return _ToneValid;
}
}
}
public long Length public float[] ToneWeigth { get; private set; }
{
get { return _Stream.Length; }
}
public byte[] Buffer
{
get { return _Stream.ToArray(); }
}
public float[] ToneWeigth
{
get
{
lock (_AnalysisBufferLock)
{
return _ToneWeigth;
}
}
}
public void Reset() public void Reset()
{ {
_Stream.SetLength(0L); lock (_Stream)
_AnalysisBuffer = new Int16[_AnalysisBuffer.Length]; {
_ToneValid = false; _Stream.SetLength(0L);
_ToneAbs = 0; _NewSamples = false;
_Tone = 0; }
_NewSamples = false; ToneValid = false;
ToneAbs = 0;
Tone = 0;
} }
public void Add(byte[] bytes) private void _Add(byte[] bytes)
{ {
_Stream.Write(bytes, 0, bytes.Length); _Stream.Write(bytes, 0, bytes.Length);
} }
@@ -440,9 +384,9 @@ namespace Vocaluxe.Base
// voice passthrough (send data to playback-device) // voice passthrough (send data to playback-device)
//if (assigned(fVoiceStream)) then //if (assigned(fVoiceStream)) then
//fVoiceStream.WriteData(Buffer, BufferSize); //fVoiceStream.WriteData(Buffer, BufferSize);
lock (_AnalysisBufferLock) lock (_Stream)
{ {
Add(buffer); _Add(buffer);
_NewSamples = true; _NewSamples = true;
} }
} }
@@ -452,67 +396,67 @@ namespace Vocaluxe.Base
if (!_NewSamples) if (!_NewSamples)
return; return;
lock (_AnalysisBufferLock) lock (_Stream)
{ {
int len = _AnalysisBuffer.Length * 2; if (_Stream.Length >= _AnalysisByteBufLen)
if (_Stream.Length >= len)
{ {
var buf = new byte[len]; _Stream.Position -= _AnalysisByteBufLen;
_Stream.Position -= len; _Stream.Read(_AnalysisByteBuffer, 0, _AnalysisByteBufLen);
_Stream.Read(buf, 0, len);
var b = new byte[2];
for (int i = 0; i < _AnalysisBuffer.Length; i++)
{
b[0] = buf[i * 2];
b[1] = buf[i * 2 + 1];
_AnalysisBuffer[i] = BitConverter.ToInt16(b, 0);
}
} }
_NewSamples = false; _NewSamples = false;
} }
for (int i = 0; i < _AnalysisBufLen; i++)
_AnalysisBuffer[i] = BitConverter.ToInt16(_AnalysisByteBuffer, i * 2);
try try
{ {
// find maximum volume // find maximum volume
_MaxVolume = 0; _FindMaxVolume();
for (int i = 0; i < _AnalysisBuffer.Length / 4; i++)
{
float volume = Math.Abs((float)_AnalysisBuffer[i]) / Int16.MaxValue;
if (volume > MaxVolume)
_MaxVolume = volume;
}
_AnalyzeByAutocorrelation(_MaxVolume >= 0.02f); if (MaxVolume >= 0.02)
_AnalyzeByAutocorrelation();
else
ToneValid = false;
} }
catch (Exception) {} catch (Exception) {}
} }
private void _AnalyzeByAutocorrelation(bool valid) private void _FindMaxVolume()
{ {
if (!valid) short maxVolume = 0;
for (int i = 0; i < _AnalysisBuffer.Length / 4; i++)
{ {
_ToneValid = false; short volume = _AnalysisBuffer[i];
return; if (volume < 0)
{
if (volume == Int16.MinValue)
{
maxVolume = Int16.MaxValue;
break;
}
volume = (short)-volume;
}
if (volume > maxVolume)
maxVolume = volume;
} }
_MaxVolume = (double)maxVolume / Int16.MaxValue;
}
const double halftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave) private void _AnalyzeByAutocorrelation()
{
// prepare to analyze // prepare to analyze
double maxWeight = -1.0; double maxWeight = -1.0;
double minWeight = 1.0; double minWeight = 1.0;
int maxTone = -1; int maxTone = -1;
var weigth = new float[_NumHalfTones];
// 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 = 0; toneIndex < NumHalfTones; toneIndex++)
{ {
double curFreq = _BaseToneFreq * Math.Pow(halftoneBase, toneIndex); double curWeight = _AnalyzeAutocorrelationTone(toneIndex);
double curWeight = _AnalyzeAutocorrelationFreq(curFreq);
if (curWeight > maxWeight) if (curWeight > maxWeight)
{ {
@@ -523,42 +467,39 @@ namespace Vocaluxe.Base
if (curWeight < minWeight) if (curWeight < minWeight)
minWeight = curWeight; minWeight = curWeight;
weigth[toneIndex] = (float)curWeight; _TmpWeight[toneIndex] = (float)curWeight;
} }
if (maxWeight - minWeight > 0.01) if (maxWeight - minWeight > 0.01)
{ {
for (int i = 0; i < weigth.Length; i++) Array.Copy(_TmpWeight, ToneWeigth, NumHalfTones);
_ToneWeigth[i] = weigth[i];
_ToneAbs = maxTone; ToneAbs = maxTone;
_Tone = maxTone % 12; Tone = maxTone % 12;
_ToneValid = true; ToneValid = true;
} }
else else
_ToneValid = false; ToneValid = false;
} }
private double _AnalyzeAutocorrelationFreq(double freq) private double _AnalyzeAutocorrelationTone(int tone)
{ {
int sampleIndex = 0; // index of sample to analyze int samplesPerPeriod = _SamplesPerPeriodPerTone[tone]; // samples in one period
var samplesPerPeriod = (int)Math.Round(44100.0 / freq); // samples in one period
int correlatingSampleIndex = sampleIndex + samplesPerPeriod; // index of sample one period ahead
double accumDist = 0.0; // accumulated distances int accumDist = 0; // accumulated distances
// compare correlating samples // compare correlating samples
while (correlatingSampleIndex < _AnalysisBuffer.Length) 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 (correlation: 1-dist) to corresponding sample in next period // calc distance (correlation: 1-dist/IntMax) to corresponding sample in next period
// distance (0=equal .. 1=totally different) between correlated samples // distance (0=equal .. IntMax=totally different) between correlated samples
double dist = Math.Abs((double)_AnalysisBuffer[sampleIndex] - _AnalysisBuffer[correlatingSampleIndex]) / Int16.MaxValue; int dist = Math.Abs(_AnalysisBuffer[sampleIndex] - _AnalysisBuffer[correlatingSampleIndex]);
accumDist += dist; accumDist += dist;
sampleIndex++;
correlatingSampleIndex++;
} }
return 1 - accumDist / _AnalysisBuffer.Length; return 1.0 - (double)accumDist / Int16.MaxValue / _AnalysisBufLen;
} }
public void Dispose() public void Dispose()
+1 -1
View File
@@ -192,7 +192,7 @@ namespace Vocaluxe.Lib.Sound
if (!_Initialized) if (!_Initialized)
return 0; return 0;
return _Buffer[player].NumHalfTones; return CBuffer.NumHalfTones;
} }
public float[] ToneWeigth(int player) public float[] ToneWeigth(int player)
+1 -1
View File
@@ -246,7 +246,7 @@ namespace Vocaluxe.Lib.Sound
if (!_Initialized) if (!_Initialized)
return 0; return 0;
return _Buffer[player].NumHalfTones; return CBuffer.NumHalfTones;
} }
public float[] ToneWeigth(int player) public float[] ToneWeigth(int player)