Extract class for testing pitchtrackers
Fix some typos
This commit is contained in:
1 parent
9bf12315b7
commit
001884ed9d
5 files changed
+398
-333
No files matched your search
@@ -71,7 +71,7 @@ int PtAKF_GetNumHalfTones(){
|
|||||||
return PtAKF::GetNumHalfTones();
|
return PtAKF::GetNumHalfTones();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PtAKF_SetVolumeTreshold(PtAKF* analyzer, float threshold){
|
void PtAKF_SetVolumeThreshold(PtAKF* analyzer, float threshold){
|
||||||
if(!analyzer)
|
if(!analyzer)
|
||||||
return;
|
return;
|
||||||
analyzer->SetVolumeThreshold(threshold);
|
analyzer->SetVolumeThreshold(threshold);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ DllExport void PtDyWa_Free(PtDyWa* analyzer);
|
|||||||
DllExport void PtDyWa_SetVolumeTreshold(PtDyWa* analyzer,float threshold);
|
DllExport void PtDyWa_SetVolumeTreshold(PtDyWa* analyzer,float threshold);
|
||||||
DllExport float PtDyWa_GetVolumeThreshold(PtDyWa* analyzer);
|
DllExport float PtDyWa_GetVolumeThreshold(PtDyWa* analyzer);
|
||||||
DllExport void PtDyWa_InputByte(PtDyWa* analyzer, char* data, int sampleCt);
|
DllExport void PtDyWa_InputByte(PtDyWa* analyzer, char* data, int sampleCt);
|
||||||
DllExport double PtDyWa_FindNote(PtDyWa* analyzer);
|
DllExport double PtDyWa_FindNote(PtDyWa* analyzer, float* maxVolume);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
public ref class CTone {
|
public ref class CTone {
|
||||||
|
|||||||
@@ -18,28 +18,12 @@
|
|||||||
#define TEST_PITCH
|
#define TEST_PITCH
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
#if TEST_PITCH
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Pitch;
|
|
||||||
using Vocaluxe.Lib.Sound.Record.Pitch;
|
|
||||||
using Vocaluxe.Lib.Sound.Record.PitchTracker;
|
using Vocaluxe.Lib.Sound.Record.PitchTracker;
|
||||||
using VocaluxeLib;
|
|
||||||
using VocaluxeLib.Utils;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Vocaluxe.Lib.Sound.Record
|
namespace Vocaluxe.Lib.Sound.Record
|
||||||
{
|
{
|
||||||
class CBuffer : IDisposable
|
class CBuffer : IDisposable
|
||||||
{
|
{
|
||||||
#if TEST_PITCH
|
|
||||||
//Half tones: C C# D D# E F F# G G# A A# B
|
|
||||||
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)
|
|
||||||
#endif
|
|
||||||
private CPitchTracker _PitchTracker = new CPtAKF();
|
private CPitchTracker _PitchTracker = new CPtAKF();
|
||||||
|
|
||||||
private float _MaxVolume;
|
private float _MaxVolume;
|
||||||
@@ -50,8 +34,12 @@ namespace Vocaluxe.Lib.Sound.Record
|
|||||||
ToneWeigths = new float[GetNumHalfTones()];
|
ToneWeigths = new float[GetNumHalfTones()];
|
||||||
Reset();
|
Reset();
|
||||||
#if TEST_PITCH
|
#if TEST_PITCH
|
||||||
_TestPitchDetection();
|
CPitchTrackerTest tester = new CPitchTrackerTest();
|
||||||
Reset();
|
tester.AddAnalyzer(new CAnalyzer());
|
||||||
|
tester.AddAnalyzer(new CPtAKF());
|
||||||
|
tester.AddAnalyzer(new CPtDyWa());
|
||||||
|
tester.AddAnalyzer(new CPtSharp());
|
||||||
|
tester.RunTest();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +97,7 @@ namespace Vocaluxe.Lib.Sound.Record
|
|||||||
|
|
||||||
public void AnalyzeBuffer()
|
public void AnalyzeBuffer()
|
||||||
{
|
{
|
||||||
//_MaxVolume = _Analyzer.GetPeak() / 43 + 1;
|
int tone = _PitchTracker.GetNote(out _MaxVolume, ToneWeigths);
|
||||||
int tone = _PitchTracker.GetNote(out _MaxVolume, ToneWeigths); //(int)Math.Round(_Analyzer.FindNote()); //
|
|
||||||
if (tone >= 0)
|
if (tone >= 0)
|
||||||
{
|
{
|
||||||
ToneAbs = tone;
|
ToneAbs = tone;
|
||||||
@@ -135,316 +122,5 @@ namespace Vocaluxe.Lib.Sound.Record
|
|||||||
_PitchTracker = null;
|
_PitchTracker = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TEST_PITCH
|
|
||||||
private static bool _PitchTestRun;
|
|
||||||
|
|
||||||
private static string _ToneToNote(int tone, bool withOctave = true)
|
|
||||||
{
|
|
||||||
if (tone < 0)
|
|
||||||
return "inv.";
|
|
||||||
tone += 24;
|
|
||||||
string[] notes = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
|
||||||
string result = notes[tone % 12];
|
|
||||||
if (withOctave)
|
|
||||||
result += (tone / 12);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _TestPitchDetection()
|
|
||||||
{
|
|
||||||
if (_PitchTestRun)
|
|
||||||
return;
|
|
||||||
_PitchTestRun = true;
|
|
||||||
const int toneFrom = 0;
|
|
||||||
const int toneTo = -1; // 47; //B5
|
|
||||||
Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(toneTo));
|
|
||||||
byte[] data;
|
|
||||||
byte[] data2 = new byte[2048];
|
|
||||||
int ok = 0;
|
|
||||||
int tests = 0;
|
|
||||||
double angle = 0;
|
|
||||||
for (int distort = 0; distort < 10; distort++)
|
|
||||||
{
|
|
||||||
for (int tone = toneFrom; tone <= toneTo; tone++)
|
|
||||||
{
|
|
||||||
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone), 44100, ref angle, out data);
|
|
||||||
_Distort(data, tone, distort);
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
Buffer.BlockCopy(data, i * 2048, data2, 0, 2048);
|
|
||||||
ProcessNewBuffer(data2);
|
|
||||||
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 + ")");
|
|
||||||
CWavFile file = new CWavFile();
|
|
||||||
file.Create(tone + "-" + distort + ".wav", 1, 44100, 16);
|
|
||||||
file.Write16BitSamples(data, 1);
|
|
||||||
file.Close();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ok++;
|
|
||||||
tests++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*if (_TestFile("toneG3.wav", 19))
|
|
||||||
ok++;
|
|
||||||
tests++;
|
|
||||||
if (_TestFile("toneG3Miss.wav", 19))
|
|
||||||
ok++;
|
|
||||||
tests++;
|
|
||||||
if (_TestFile("toneG4.wav", 31))
|
|
||||||
ok++;
|
|
||||||
tests++;*/
|
|
||||||
_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();
|
|
||||||
sw.Start();
|
|
||||||
const int repeats = 100;
|
|
||||||
for (int i = 0; i < repeats; i++)
|
|
||||||
{
|
|
||||||
ProcessNewBuffer(data);
|
|
||||||
AnalyzeBuffer();
|
|
||||||
}
|
|
||||||
sw.Stop();
|
|
||||||
string msg = "Analyser: Errors: " + (tests - ok) + "/" + tests + "; Speed: " +
|
|
||||||
(int)(repeats / (sw.ElapsedMilliseconds / 1000.0)) + " buffers/s";
|
|
||||||
Console.WriteLine(msg);
|
|
||||||
MessageBox.Show(msg);
|
|
||||||
Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct STimedNote
|
|
||||||
{
|
|
||||||
public int Time, Note;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _TestFile(string fileName, string testFileName, ref int ct, ref int ok)
|
|
||||||
{
|
|
||||||
if (!File.Exists(testFileName))
|
|
||||||
{
|
|
||||||
ok = ct = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
List<STimedNote> tones = new List<STimedNote>();
|
|
||||||
using (StreamReader reader = new StreamReader(testFileName))
|
|
||||||
{
|
|
||||||
String line;
|
|
||||||
while ((line = reader.ReadLine()) != null)
|
|
||||||
{
|
|
||||||
int p = line.IndexOf(' ');
|
|
||||||
if (p < 0)
|
|
||||||
continue;
|
|
||||||
STimedNote note;
|
|
||||||
note.Time = int.Parse(line.Substring(0, p));
|
|
||||||
note.Note = int.Parse(line.Substring(p + 1));
|
|
||||||
tones.Add(note);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_TestFile(fileName, tones, ref ct, ref ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool _TestFile(string fileName, int tone)
|
|
||||||
{
|
|
||||||
STimedNote note;
|
|
||||||
note.Note = tone;
|
|
||||||
note.Time = 46;
|
|
||||||
List<STimedNote> tones = new List<STimedNote> {note};
|
|
||||||
int ct = 0;
|
|
||||||
int ok = 0;
|
|
||||||
_TestFile(fileName, tones, ref ct, ref ok);
|
|
||||||
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();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!wavFile.Open(fileName))
|
|
||||||
return;
|
|
||||||
if (wavFile.BitsPerSample != 16)
|
|
||||||
{
|
|
||||||
wavFile.Close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int samplesRead = 0;
|
|
||||||
int curTimeIndex = -1;
|
|
||||||
int curNote = -1;
|
|
||||||
const int maxSamplesPerBatch = 512;
|
|
||||||
CPitchTracker analyzer2 = new CAnalyzer();
|
|
||||||
CPitchTracker analyzer3 = new CPtDyWa();
|
|
||||||
CPitchTracker analyzer4 = new CPtSharp();
|
|
||||||
int[] detectedTones = new int[4];
|
|
||||||
int[] oks = new int[4];
|
|
||||||
float dummyMaxVol;
|
|
||||||
float[] dummyWeights = new float[ToneWeigths.Length];
|
|
||||||
int lCt = 0;
|
|
||||||
while (wavFile.NumSamplesLeft > maxSamplesPerBatch)
|
|
||||||
{
|
|
||||||
byte[] samples = wavFile.GetNextSamples16BitAsBytes(maxSamplesPerBatch, 1);
|
|
||||||
samplesRead += samples.Length / 2;
|
|
||||||
int time = samplesRead * 1000 / wavFile.SampleRate;
|
|
||||||
ProcessNewBuffer(samples);
|
|
||||||
AnalyzeBuffer();
|
|
||||||
analyzer2.Input(samples);
|
|
||||||
analyzer3.Input(samples);
|
|
||||||
analyzer4.Input(samples);
|
|
||||||
while (curTimeIndex + 1 < tones.Count && time >= tones[curTimeIndex + 1].Time)
|
|
||||||
{
|
|
||||||
curTimeIndex++;
|
|
||||||
curNote = tones[curTimeIndex].Note;
|
|
||||||
}
|
|
||||||
if (curNote < 0)
|
|
||||||
continue;
|
|
||||||
detectedTones[0] = ToneValid ? ToneAbs : -1;
|
|
||||||
detectedTones[1] = analyzer2.GetNote(out dummyMaxVol, dummyWeights);
|
|
||||||
detectedTones[2] = analyzer3.GetNote(out dummyMaxVol, dummyWeights);
|
|
||||||
detectedTones[3] = analyzer4.GetNote(out dummyMaxVol, dummyWeights);
|
|
||||||
lCt++;
|
|
||||||
/*if (!ToneValid)
|
|
||||||
CBase.Log.LogDebug("Note " + _ToneToNote(curNote) + " at " + time + "ms not detected");
|
|
||||||
else if (ToneAbs % 12 != curNote % 12)
|
|
||||||
CBase.Log.LogDebug("Note " + _ToneToNote(curNote) + " at " + time + "ms wrongly detected as " + _ToneToNote(ToneAbs));
|
|
||||||
else
|
|
||||||
ok++;*/
|
|
||||||
bool[] valids = new bool[4];
|
|
||||||
bool error = false;
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
valids[i] = _IsNoteValid(detectedTones[i], time, tones);
|
|
||||||
if (valids[i])
|
|
||||||
oks[i]++;
|
|
||||||
else
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
string msg = "Note " + _ToneToNote(curNote) + " at " + time + "ms detected as ";
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
msg += _ToneToNote(detectedTones[i]) + (valids[i] ? "" : "(!)") + "; ";
|
|
||||||
CBase.Log.LogDebug(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
string msg2 = "Detected ";
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
msg2 += oks[i] + "|" + (lCt - oks[i]) + " ";
|
|
||||||
CBase.Log.LogDebug(msg2);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Error on file " + fileName + ": " + e);
|
|
||||||
}
|
|
||||||
wavFile.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static short[] _GetDistort(int len, int tone, int type)
|
|
||||||
{
|
|
||||||
short[] sdata = new short[len];
|
|
||||||
if (type < 9)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
double angle = 0;
|
|
||||||
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone + newTone), 44100, ref angle, out data2);
|
|
||||||
Buffer.BlockCopy(data2, 0, sdata, 0, len * 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Random r = new Random(0xBEEF);
|
|
||||||
for (int i = 0; i < sdata.Length; i++)
|
|
||||||
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++)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void _GetSineWave(double freq, int sampleRate, ref double angle, out byte[] data)
|
|
||||||
{
|
|
||||||
const short max = short.MaxValue;
|
|
||||||
const int len = 4096; //sampleRate * durationMs / 1000;
|
|
||||||
short[] data16Bit = new short[len];
|
|
||||||
for (int i = 0; i < len; i++)
|
|
||||||
data16Bit[i] = (short)(Math.Sin(2 * Math.PI * i / sampleRate * freq + angle) * max);
|
|
||||||
angle = 2 * Math.PI * len / sampleRate * freq + angle;
|
|
||||||
angle = angle % (2 * Math.PI);
|
|
||||||
data = new byte[data16Bit.Length * 2];
|
|
||||||
Buffer.BlockCopy(data16Bit, 0, data, 0, data.Length);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,388 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using VocaluxeLib;
|
||||||
|
using VocaluxeLib.Utils;
|
||||||
|
|
||||||
|
namespace Vocaluxe.Lib.Sound.Record.PitchTracker
|
||||||
|
{
|
||||||
|
class CPitchTrackerTest
|
||||||
|
{
|
||||||
|
//Half tones: C C# D D# E F F# G G# A A# B
|
||||||
|
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 readonly List<CPitchTracker> _Analyzers = new List<CPitchTracker>();
|
||||||
|
private int[] _Tones;
|
||||||
|
private float[][] _Weights;
|
||||||
|
private float _MaxVolume;
|
||||||
|
private int _CurTestCount;
|
||||||
|
private int[] _CurPassedCount;
|
||||||
|
private int _TestCount;
|
||||||
|
private int[] _PassedCount;
|
||||||
|
private int[] _SamplesPerSec;
|
||||||
|
|
||||||
|
private static bool _IsRun;
|
||||||
|
|
||||||
|
public void AddAnalyzer(CPitchTracker analyzer)
|
||||||
|
{
|
||||||
|
_Analyzers.Add(analyzer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Init
|
||||||
|
private void _InitTests()
|
||||||
|
{
|
||||||
|
_Tones = new int[_Analyzers.Count];
|
||||||
|
_TestCount = 0;
|
||||||
|
_PassedCount = new int[_Analyzers.Count];
|
||||||
|
_CurTestCount = 0;
|
||||||
|
_CurPassedCount = new int[_Analyzers.Count];
|
||||||
|
_Weights = new float[_Analyzers.Count][];
|
||||||
|
for (int i = 0; i < _Analyzers.Count; i++)
|
||||||
|
_Weights[i] = new float[_Analyzers[i].GetNumHalfTones()];
|
||||||
|
_SamplesPerSec = new int[_Analyzers.Count];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _InitNext(string lastTest)
|
||||||
|
{
|
||||||
|
string msg = "Errors " + lastTest + ": ";
|
||||||
|
for (int i = 0; i < _CurPassedCount.Length; i++)
|
||||||
|
{
|
||||||
|
msg += _CurTestCount - _CurPassedCount[i] + " ";
|
||||||
|
_PassedCount[i] += _CurPassedCount[i];
|
||||||
|
_CurPassedCount[i] = 0;
|
||||||
|
}
|
||||||
|
msg += "of " + _CurTestCount;
|
||||||
|
CBase.Log.LogDebug(msg);
|
||||||
|
_TestCount += _CurTestCount;
|
||||||
|
_CurTestCount = 0;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void RunTest(bool reRun = false)
|
||||||
|
{
|
||||||
|
if (_IsRun && !reRun)
|
||||||
|
return;
|
||||||
|
_IsRun = true;
|
||||||
|
|
||||||
|
_InitTests();
|
||||||
|
|
||||||
|
_TestSines();
|
||||||
|
_InitNext("Sines");
|
||||||
|
|
||||||
|
_TestFile("toneG3.wav", 19);
|
||||||
|
_InitNext("Real G3");
|
||||||
|
|
||||||
|
_TestFile("toneG3Miss.wav", 19);
|
||||||
|
_InitNext("Real G3 with miss. fundamental");
|
||||||
|
|
||||||
|
_TestFile("toneG4.wav", 31);
|
||||||
|
_InitNext("Real G4");
|
||||||
|
|
||||||
|
_TestFile("whistling3.wav", "whistling3.txt");
|
||||||
|
_InitNext("High whistling");
|
||||||
|
|
||||||
|
_TestFile("sClausVoc.wav", "sClausVoc.txt");
|
||||||
|
_InitNext("Real song");
|
||||||
|
|
||||||
|
_TestSpeed();
|
||||||
|
CBase.Log.LogDebug("Finished: ");
|
||||||
|
for (int i = 0; i < _Analyzers.Count; i++)
|
||||||
|
{
|
||||||
|
string msg = _Analyzers[i].GetType().Name + ":";
|
||||||
|
msg += " Errors=" + (_TestCount - _PassedCount[i]);
|
||||||
|
msg += " Passed=" + _PassedCount[i];
|
||||||
|
msg += " Total=" + _TestCount;
|
||||||
|
msg += " Speed=" + _SamplesPerSec[i] + "samples/s (=" + (_SamplesPerSec[i] / 44100) + "rec.s/s)";
|
||||||
|
CBase.Log.LogDebug(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _TestSpeed()
|
||||||
|
{
|
||||||
|
const int samplesPerBuffer = 512;
|
||||||
|
byte[] data;
|
||||||
|
byte[] data2 = new byte[samplesPerBuffer * 2];
|
||||||
|
double angle = 0;
|
||||||
|
const int repeats = 100;
|
||||||
|
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, 5), 44100, samplesPerBuffer * repeats, ref angle, out data);
|
||||||
|
for (int i = 0; i < _Analyzers.Count; i++)
|
||||||
|
{
|
||||||
|
CPitchTracker analyzer = _Analyzers[i];
|
||||||
|
Stopwatch sw = new Stopwatch();
|
||||||
|
sw.Start();
|
||||||
|
for (int j = 0; j < repeats; j++)
|
||||||
|
{
|
||||||
|
Buffer.BlockCopy(data, 0, data2, 0, samplesPerBuffer * 2);
|
||||||
|
analyzer.Input(data2);
|
||||||
|
analyzer.GetNote(out _MaxVolume, _Weights[j]);
|
||||||
|
}
|
||||||
|
sw.Stop();
|
||||||
|
_SamplesPerSec[i] = (int)Math.Round(samplesPerBuffer * repeats / (sw.ElapsedMilliseconds / 1000.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _Process(byte[] data)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < _Analyzers.Count; j++)
|
||||||
|
{
|
||||||
|
_Analyzers[j].Input(data);
|
||||||
|
_Tones[j] = _Analyzers[j].GetNote(out _MaxVolume, _Weights[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string _ToneToNote(int tone, bool withOctave = true)
|
||||||
|
{
|
||||||
|
if (tone < 0)
|
||||||
|
return "inv.";
|
||||||
|
tone += 24;
|
||||||
|
string[] notes = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
||||||
|
string result = notes[tone % 12];
|
||||||
|
if (withOctave)
|
||||||
|
result += (tone / 12);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Sines
|
||||||
|
private void _TestSines()
|
||||||
|
{
|
||||||
|
const int toneFrom = 0;
|
||||||
|
const int toneTo = 47; //B5
|
||||||
|
const int sampleCt = 4096;
|
||||||
|
const int batchCt = 512;
|
||||||
|
byte[] data2 = new byte[batchCt * 2];
|
||||||
|
Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(toneTo));
|
||||||
|
double angle = 0;
|
||||||
|
bool[] valids = new bool[_Analyzers.Count];
|
||||||
|
for (int distort = 0; distort < 10; distort++)
|
||||||
|
{
|
||||||
|
for (int tone = toneFrom; tone <= toneTo; tone++)
|
||||||
|
{
|
||||||
|
byte[] data;
|
||||||
|
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone), 44100, sampleCt, ref angle, out data);
|
||||||
|
_Distort(data, tone, distort);
|
||||||
|
|
||||||
|
for (int i = 0; i < sampleCt / batchCt; i++)
|
||||||
|
{
|
||||||
|
Buffer.BlockCopy(data, i * batchCt * 2, data2, 0, batchCt * 2);
|
||||||
|
_Process(data2);
|
||||||
|
if (i * batchCt < 2048)
|
||||||
|
continue;
|
||||||
|
_CurTestCount++;
|
||||||
|
bool ok = true;
|
||||||
|
for (int j = 0; j < valids.Length; j++)
|
||||||
|
{
|
||||||
|
valids[j] = _Tones[j] == tone;
|
||||||
|
if (!valids[j])
|
||||||
|
ok = false;
|
||||||
|
else
|
||||||
|
_CurPassedCount[j]++;
|
||||||
|
}
|
||||||
|
if (ok)
|
||||||
|
continue;
|
||||||
|
string msg = "Note " + _ToneToNote(tone) + " detected as ";
|
||||||
|
for (int j = 0; j < valids.Length; j++)
|
||||||
|
msg += _ToneToNote(_Tones[j]) + (valids[j] ? "" : "(!)") + "; ";
|
||||||
|
CBase.Log.LogDebug(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static short[] _GetDistort(int sampleCt, int tone, int type)
|
||||||
|
{
|
||||||
|
short[] sdata = new short[sampleCt];
|
||||||
|
if (type < 9)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
double angle = 0;
|
||||||
|
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, tone + newTone), 44100, sampleCt, ref angle, out data2);
|
||||||
|
Buffer.BlockCopy(data2, 0, sdata, 0, sampleCt * 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Random r = new Random(0xBEEF);
|
||||||
|
for (int i = 0; i < sdata.Length; i++)
|
||||||
|
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++)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void _GetSineWave(double freq, int sampleRate, int sampleCt, ref double angle, out byte[] data)
|
||||||
|
{
|
||||||
|
const short max = short.MaxValue;
|
||||||
|
short[] data16Bit = new short[sampleCt];
|
||||||
|
for (int i = 0; i < sampleCt; i++)
|
||||||
|
data16Bit[i] = (short)(Math.Sin(2 * Math.PI * i / sampleRate * freq + angle) * max);
|
||||||
|
angle = 2 * Math.PI * sampleCt / sampleRate * freq + angle;
|
||||||
|
angle = angle % (2 * Math.PI);
|
||||||
|
data = new byte[data16Bit.Length * 2];
|
||||||
|
Buffer.BlockCopy(data16Bit, 0, data, 0, data.Length);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region File
|
||||||
|
private struct STimedNote
|
||||||
|
{
|
||||||
|
public int Time, Note;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _TestFile(string fileName, string testFileName)
|
||||||
|
{
|
||||||
|
if (!File.Exists(testFileName))
|
||||||
|
return;
|
||||||
|
List<STimedNote> tones = new List<STimedNote>();
|
||||||
|
using (StreamReader reader = new StreamReader(testFileName))
|
||||||
|
{
|
||||||
|
String line;
|
||||||
|
while ((line = reader.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
int p = line.IndexOf(' ');
|
||||||
|
if (p < 0)
|
||||||
|
continue;
|
||||||
|
STimedNote note;
|
||||||
|
note.Time = int.Parse(line.Substring(0, p));
|
||||||
|
note.Note = int.Parse(line.Substring(p + 1));
|
||||||
|
tones.Add(note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_TestFile(fileName, tones);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _TestFile(string fileName, int tone)
|
||||||
|
{
|
||||||
|
STimedNote note;
|
||||||
|
note.Note = tone;
|
||||||
|
note.Time = 46;
|
||||||
|
List<STimedNote> tones = new List<STimedNote> {note};
|
||||||
|
_TestFile(fileName, tones);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static 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)
|
||||||
|
{
|
||||||
|
CWavFile wavFile = new CWavFile();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!wavFile.Open(fileName))
|
||||||
|
return;
|
||||||
|
if (wavFile.BitsPerSample != 16)
|
||||||
|
{
|
||||||
|
wavFile.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int samplesRead = 0;
|
||||||
|
int curTimeIndex = -1;
|
||||||
|
int curNote = -1;
|
||||||
|
const int maxSamplesPerBatch = 512;
|
||||||
|
bool[] valids = new bool[_Tones.Length];
|
||||||
|
while (wavFile.NumSamplesLeft > maxSamplesPerBatch)
|
||||||
|
{
|
||||||
|
byte[] samples = wavFile.GetNextSamples16BitAsBytes(maxSamplesPerBatch, 1);
|
||||||
|
samplesRead += samples.Length / 2;
|
||||||
|
int time = samplesRead * 1000 / wavFile.SampleRate;
|
||||||
|
_Process(samples);
|
||||||
|
while (curTimeIndex + 1 < tones.Count && time >= tones[curTimeIndex + 1].Time)
|
||||||
|
{
|
||||||
|
curTimeIndex++;
|
||||||
|
curNote = tones[curTimeIndex].Note;
|
||||||
|
}
|
||||||
|
if (curNote < 0)
|
||||||
|
continue;
|
||||||
|
_CurTestCount++;
|
||||||
|
bool error = false;
|
||||||
|
for (int i = 0; i < _Tones.Length; i++)
|
||||||
|
{
|
||||||
|
valids[i] = _IsNoteValid(_Tones[i], time, tones);
|
||||||
|
if (valids[i])
|
||||||
|
_CurPassedCount[i]++;
|
||||||
|
else
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
string msg = "Note " + _ToneToNote(curNote) + " at " + time + "ms detected as ";
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
msg += _ToneToNote(_Tones[i]) + (valids[i] ? "" : "(!)") + "; ";
|
||||||
|
CBase.Log.LogDebug(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error on file " + fileName + ": " + e);
|
||||||
|
}
|
||||||
|
wavFile.Close();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -268,6 +268,7 @@
|
|||||||
<Compile Include="Lib\Sound\Record\CRecordBase.cs" />
|
<Compile Include="Lib\Sound\Record\CRecordBase.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\DirectSound\CSampleDataEventArgs.cs" />
|
<Compile Include="Lib\Sound\Record\DirectSound\CSampleDataEventArgs.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\DirectSound\CSoundCardSource.cs" />
|
<Compile Include="Lib\Sound\Record\DirectSound\CSoundCardSource.cs" />
|
||||||
|
<Compile Include="Lib\Sound\Record\PitchTracker\CPitchTrackerTest.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\CPtAKF.cs" />
|
<Compile Include="Lib\Sound\Record\PitchTracker\CPtAKF.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\CPtDyWa.cs" />
|
<Compile Include="Lib\Sound\Record\PitchTracker\CPtDyWa.cs" />
|
||||||
<Compile Include="Lib\Sound\Record\PitchTracker\CPtSharp.cs" />
|
<Compile Include="Lib\Sound\Record\PitchTracker\CPtSharp.cs" />
|
||||||
|
|||||||
Reference in new issue
Block a user