Fix bugs in AKF detection
Add CWavFile.cs for reading wav files Add more test cases
This commit is contained in:
1 parent
614a7e0ed2
commit
d584206a7f
9 files changed
+524
-17
No files matched your search
@@ -4,7 +4,7 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
float* short2FloatArray(short* in, size_t len){
|
float* short2FloatArray(short* in, size_t len){
|
||||||
const float maxShort = 32768.0; //maximum abs value of a short
|
const float maxShort = 32767.0;
|
||||||
|
|
||||||
float* result = static_cast<float*>(malloc(len * sizeof(float)));
|
float* result = static_cast<float*>(malloc(len * sizeof(float)));
|
||||||
float* curOut = result;
|
float* curOut = result;
|
||||||
@@ -17,7 +17,7 @@ float* short2FloatArray(short* in, size_t len){
|
|||||||
}
|
}
|
||||||
|
|
||||||
double* short2DoubleArray(short* in, size_t len, bool scale = true){
|
double* short2DoubleArray(short* in, size_t len, bool scale = true){
|
||||||
const double maxShort = 32768.0; //maximum abs value of a short
|
const double maxShort = 32767.0;
|
||||||
|
|
||||||
double* result = static_cast<double*>(malloc(len * sizeof(double)));
|
double* result = static_cast<double*>(malloc(len * sizeof(double)));
|
||||||
double* curOut = result;
|
double* curOut = result;
|
||||||
|
|||||||
@@ -75,7 +75,11 @@ DllExport void PtAKF_InputByte(PtAKF* analyzer, char* data, int sampleCt){
|
|||||||
if(sampleCt <= 0 || !analyzer)
|
if(sampleCt <= 0 || !analyzer)
|
||||||
return;
|
return;
|
||||||
short* dataShort = static_cast<short*>(static_cast<void*>(data));
|
short* dataShort = static_cast<short*>(static_cast<void*>(data));
|
||||||
analyzer->input(dataShort, dataShort + sampleCt);
|
float* dataFloat = short2FloatArray(static_cast<short*>(static_cast<void*>(data)), sampleCt);
|
||||||
|
analyzer->input(dataFloat, dataFloat + sampleCt);
|
||||||
|
freeFloatArray(dataFloat);
|
||||||
|
|
||||||
|
//analyzer->input(dataShort, dataShort + sampleCt);
|
||||||
}
|
}
|
||||||
|
|
||||||
DllExport int PtAKF_GetNote(PtAKF* analyzer, double* maxVolume, float* weights){
|
DllExport int PtAKF_GetNote(PtAKF* analyzer, double* maxVolume, float* weights){
|
||||||
|
|||||||
Binary file not shown.
+87
-6
@@ -24,9 +24,9 @@ float* PtAKF::_SamplesPerPeriodPerTone = NULL;
|
|||||||
PtAKF::PtAKF(unsigned step){
|
PtAKF::PtAKF(unsigned step){
|
||||||
_InitCount++;
|
_InitCount++;
|
||||||
if(_InitCount == 1){
|
if(_InitCount == 1){
|
||||||
_SamplesPerPeriodPerTone = new float[_MaxHalfTone + 1];
|
_SamplesPerPeriodPerTone = new float[_MaxHalfTone + 1 + _HalfTonesAdd];
|
||||||
//Init Array to avoid costly calculations
|
//Init Array to avoid costly calculations
|
||||||
for (int toneIndex = 0; toneIndex <= _MaxHalfTone; toneIndex++)
|
for (int toneIndex = 0; toneIndex <= _MaxHalfTone + _HalfTonesAdd; toneIndex++)
|
||||||
{
|
{
|
||||||
double freq = BaseToneFrequency * pow(HalftoneBase, toneIndex);
|
double freq = BaseToneFrequency * pow(HalftoneBase, toneIndex);
|
||||||
_SamplesPerPeriodPerTone[toneIndex] = static_cast<float>(44100.0 / freq); // samples in one period
|
_SamplesPerPeriodPerTone[toneIndex] = static_cast<float>(44100.0 / freq); // samples in one period
|
||||||
@@ -61,12 +61,23 @@ int PtAKF::_GetNote(float* samples, double* maxVolume, float* weights){
|
|||||||
if(vol > maxVolumeL)
|
if(vol > maxVolumeL)
|
||||||
maxVolumeL = vol;
|
maxVolumeL = vol;
|
||||||
}
|
}
|
||||||
maxVolumeL /= 32767.;
|
//maxVolumeL /= 32767.;
|
||||||
*maxVolume = maxVolumeL;
|
*maxVolume = maxVolumeL;
|
||||||
|
|
||||||
|
//Scale the samples to have a peak of 1 to better decide between voiced/unvoiced
|
||||||
|
//float scaleVal = 1 / maxVolumeL;
|
||||||
|
//for(int i = 0; i < _SampleCt; i++){
|
||||||
|
// samples[i] *= scaleVal;
|
||||||
|
//}
|
||||||
|
|
||||||
float maxWeight = -1;
|
float maxWeight = -1;
|
||||||
float minWeight = 1;
|
float minWeight = 1;
|
||||||
int maxTone = -1;
|
int maxTone = -1;
|
||||||
|
|
||||||
|
//Attention: We have a peak at lag 0 that might stretch that far, that we detect a wrong "peak" at _MaxHalfTone
|
||||||
|
//Because of that we filter out all tones that are past the last zero crossing from below but keep tones with decreasing weights (going towards zero crossing from above)
|
||||||
|
int lastValidTone = 0;
|
||||||
|
float lastWeight = 1.f;
|
||||||
for (int toneIndex = _MinHalfTone; toneIndex <= _MaxHalfTone; toneIndex++)
|
for (int toneIndex = _MinHalfTone; toneIndex <= _MaxHalfTone; toneIndex++)
|
||||||
{
|
{
|
||||||
float curWeight = _AnalyzeToneFunc(samples, toneIndex);
|
float curWeight = _AnalyzeToneFunc(samples, toneIndex);
|
||||||
@@ -81,18 +92,61 @@ int PtAKF::_GetNote(float* samples, double* maxVolume, float* weights){
|
|||||||
minWeight = curWeight;
|
minWeight = curWeight;
|
||||||
|
|
||||||
weights[toneIndex - _MinHalfTone] = curWeight;
|
weights[toneIndex - _MinHalfTone] = curWeight;
|
||||||
|
|
||||||
|
//Filter:
|
||||||
|
if(lastWeight > curWeight || (lastWeight > 0.f && curWeight <= 0.f))
|
||||||
|
lastValidTone = toneIndex - 1;
|
||||||
|
lastWeight = curWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(maxWeight - minWeight > 0.01){
|
if(maxTone==_MaxHalfTone){
|
||||||
|
//We might have caught the lag 0 peak so go a bit further to check for other zero crossings (or we won't be able to detect _MaxHalfTone)
|
||||||
|
float lastWeight = _FastAKF(samples, _SamplesPerPeriodPerTone[_MaxHalfTone]);
|
||||||
|
for(int toneIndex = _MaxHalfTone+1; toneIndex <= _MaxHalfTone + _HalfTonesAdd; toneIndex++){
|
||||||
|
float curWeight = _FastAKF(samples, _SamplesPerPeriodPerTone[toneIndex]);
|
||||||
|
if(lastWeight > curWeight || (lastWeight > 0.f && curWeight <= 0.f)){
|
||||||
|
lastValidTone = toneIndex - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastWeight = curWeight;
|
||||||
|
}
|
||||||
|
if(lastValidTone < maxTone){
|
||||||
|
maxWeight = -1.f;
|
||||||
|
minWeight = 1.f;
|
||||||
|
for(int toneIndex = _MinHalfTone; toneIndex <= lastValidTone; toneIndex++){
|
||||||
|
float curWeight = weights[toneIndex-_MinHalfTone];
|
||||||
|
if (curWeight > maxWeight)
|
||||||
|
{
|
||||||
|
maxWeight = curWeight;
|
||||||
|
maxTone = toneIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curWeight < minWeight)
|
||||||
|
minWeight = curWeight;
|
||||||
|
}
|
||||||
|
//Set all invalid weights to 0
|
||||||
|
for(int toneIndex = lastValidTone + 1;toneIndex < _MaxHalfTone; toneIndex++){
|
||||||
|
weights[toneIndex - _MinHalfTone]=0.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float energy = _FastAKF(samples, 0);
|
||||||
|
float maxAKF = _FastAKF(samples, _SamplesPerPeriodPerTone[maxTone]);
|
||||||
|
//printf("%g %g\n",energy, maxAKF);
|
||||||
|
|
||||||
|
//if(maxWeight - minWeight > 0.025){
|
||||||
|
if(maxAKF >= 0.3 * energy){
|
||||||
return maxTone;
|
return maxTone;
|
||||||
}else return -1;
|
}else return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
float PtAKF::_AnalyzeToneFunc(float* samples, int toneIndex){
|
float PtAKF::_AnalyzeToneFunc(float* samples, int toneIndex){
|
||||||
// Use method by Kobayashi and Shimamura (1995): Combine AKF and AMDF to a new f(z)=AKF(z)/(AMDF(z)+k) with k=1
|
// Use method by Kobayashi and Shimamura (2001): Combine AKF and AMDF to a new f(z)=AKF(z)/(AMDF(z)+k) with k=1
|
||||||
|
|
||||||
float samplesPerPeriodD = _SamplesPerPeriodPerTone[toneIndex]; // samples in one period
|
float samplesPerPeriodD = _SamplesPerPeriodPerTone[toneIndex]; // samples in one period
|
||||||
int samplesPerPeriod = static_cast<int>(samplesPerPeriodD);
|
int samplesPerPeriod = static_cast<int>(samplesPerPeriodD);
|
||||||
|
int maxSampleCt = static_cast<int>(_SamplesPerPeriodPerTone[0]);
|
||||||
float fHigh = samplesPerPeriodD - samplesPerPeriod;
|
float fHigh = samplesPerPeriodD - samplesPerPeriod;
|
||||||
float fLow = 1.0f - fHigh;
|
float fLow = 1.0f - fHigh;
|
||||||
|
|
||||||
@@ -114,7 +168,34 @@ float PtAKF::_AnalyzeToneFunc(float* samples, int toneIndex){
|
|||||||
accumDistAKF /= _SampleCt;
|
accumDistAKF /= _SampleCt;
|
||||||
accumDistAMDF /= sampleIndex;
|
accumDistAMDF /= sampleIndex;
|
||||||
|
|
||||||
return accumDistAKF / (accumDistAMDF * 32767.f + 32767.f); // Need to scale AKF by MAX^2 and AMDF by MAX, so do some maths to divide only once
|
//return accumDistAKF / (accumDistAMDF * 32767.f + 32767.f * 32767.f); // Need to scale AKF by MAX^2 and AMDF by MAX, so do some maths to divide only once
|
||||||
|
float result = accumDistAKF / (accumDistAMDF + 1.f);
|
||||||
|
return result; // Need to scale AKF by MAX^2 and AMDF by MAX, so do some maths to divide only once
|
||||||
|
//{toneIndex}: {accumDistAKF} ; {accumDistAMDF}; {result}
|
||||||
|
}
|
||||||
|
|
||||||
|
float PtAKF::_FastAKF(float* samples, float samplesPerPeriodD){
|
||||||
|
// Do a fast, non-scaled AKF (used for Lag 0 peak removal)
|
||||||
|
|
||||||
|
int samplesPerPeriod = static_cast<int>(samplesPerPeriodD);
|
||||||
|
int maxSampleCt = static_cast<int>(_SamplesPerPeriodPerTone[0]);
|
||||||
|
float fHigh = samplesPerPeriodD - samplesPerPeriod;
|
||||||
|
float fLow = 1.0f - fHigh;
|
||||||
|
|
||||||
|
float accumDistAKF = 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 + 1 < _SampleCt; correlatingSampleIndex++, sampleIndex++)
|
||||||
|
{
|
||||||
|
// calc distance to corresponding sample in next period
|
||||||
|
float xn = samples[sampleIndex];
|
||||||
|
float xnt = samples[correlatingSampleIndex] * fLow + samples[correlatingSampleIndex + 1] * fHigh;
|
||||||
|
accumDistAKF += xn * xnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accumDistAKF;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ public:
|
|||||||
PtAKF(unsigned step);
|
PtAKF(unsigned step);
|
||||||
~PtAKF();
|
~PtAKF();
|
||||||
|
|
||||||
void input(short* begin, short* end){
|
/** Add input data to buffer. This is thread-safe (against other functions). **/
|
||||||
|
template <typename InIt> void input(InIt begin, InIt end) {
|
||||||
_AnalysisBuf.insert(begin, end);
|
_AnalysisBuf.insert(begin, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,12 +18,14 @@ private:
|
|||||||
static int _InitCount;
|
static int _InitCount;
|
||||||
static float* _SamplesPerPeriodPerTone;
|
static float* _SamplesPerPeriodPerTone;
|
||||||
static constexpr int _MinHalfTone = 0; //C2
|
static constexpr int _MinHalfTone = 0; //C2
|
||||||
static constexpr int _MaxHalfTone = 47; //B5
|
static constexpr int _MaxHalfTone = 55;//47; //B5
|
||||||
|
static constexpr int _HalfTonesAdd = 4; //Additonal half tones to analyze to remove the peak at lag 0
|
||||||
|
|
||||||
constexpr static size_t _SampleCt = 4096;
|
constexpr static size_t _SampleCt = 2048;
|
||||||
RingBuffer<_SampleCt * 2> _AnalysisBuf;
|
RingBuffer<_SampleCt * 2> _AnalysisBuf;
|
||||||
unsigned _Step;
|
unsigned _Step;
|
||||||
|
|
||||||
static int _GetNote(float* samples, double* maxVolume, float* weights);
|
static int _GetNote(float* samples, double* maxVolume, float* weights);
|
||||||
static float _AnalyzeToneFunc(float* samples, int toneIndex);
|
static float _AnalyzeToneFunc(float* samples, int toneIndex);
|
||||||
|
static float _FastAKF(float* samples, float samplesPerPeriodD);
|
||||||
};
|
};
|
||||||
@@ -164,6 +164,9 @@ namespace Vocaluxe.Base
|
|||||||
public static void LogDebug(string text)
|
public static void LogDebug(string text)
|
||||||
{
|
{
|
||||||
_DebugLog.Add(String.Format("{0:HH:mm:ss.ffff}", DateTime.Now) + ":" + text);
|
_DebugLog.Add(String.Format("{0:HH:mm:ss.ffff}", DateTime.Now) + ":" + text);
|
||||||
|
#if DEBUG
|
||||||
|
Console.WriteLine(text);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LogSongInfo(string text)
|
public static void LogSongInfo(string text)
|
||||||
|
|||||||
@@ -19,9 +19,12 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
#if TEST_PITCH
|
#if TEST_PITCH
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using VocaluxeLib;
|
using VocaluxeLib;
|
||||||
|
using VocaluxeLib.Utils;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -30,11 +33,12 @@ namespace Vocaluxe.Lib.Sound.Record
|
|||||||
class CBuffer : IDisposable
|
class CBuffer : IDisposable
|
||||||
{
|
{
|
||||||
#if TEST_PITCH
|
#if TEST_PITCH
|
||||||
//Half tones: C C♯ D D# E F F♯ G G♯ A A# B
|
//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 _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)
|
||||||
#endif
|
#endif
|
||||||
private CPtAKF _Analyzer = new CPtAKF();
|
private CPtAKF _Analyzer = new CPtAKF();
|
||||||
|
private CAnalyzer _Analyzer2 = new CAnalyzer();
|
||||||
|
|
||||||
private double _MaxVolume;
|
private double _MaxVolume;
|
||||||
|
|
||||||
@@ -94,11 +98,12 @@ namespace Vocaluxe.Lib.Sound.Record
|
|||||||
//if (assigned(fVoiceStream)) then
|
//if (assigned(fVoiceStream)) then
|
||||||
//fVoiceStream.WriteData(Buffer, BufferSize);
|
//fVoiceStream.WriteData(Buffer, BufferSize);
|
||||||
_Analyzer.Input(buffer);
|
_Analyzer.Input(buffer);
|
||||||
|
_Analyzer2.Input(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AnalyzeBuffer()
|
public void AnalyzeBuffer()
|
||||||
{
|
{
|
||||||
//_Analyzer.Process();
|
_Analyzer2.Process();
|
||||||
//_MaxVolume = _Analyzer.GetPeak() / 43 + 1;
|
//_MaxVolume = _Analyzer.GetPeak() / 43 + 1;
|
||||||
int tone = _Analyzer.GetNote(out _MaxVolume, ToneWeigths); //(int)Math.Round(_Analyzer.FindNote()); //
|
int tone = _Analyzer.GetNote(out _MaxVolume, ToneWeigths); //(int)Math.Round(_Analyzer.FindNote()); //
|
||||||
if (tone >= 0 && _MaxVolume >= MinVolume)
|
if (tone >= 0 && _MaxVolume >= MinVolume)
|
||||||
@@ -132,7 +137,7 @@ 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;
|
tone += 24;
|
||||||
string[] notes = {"C", "C♯", "D", "D#", "E", "F", "F♯", "G", "G♯", "A", "A#", "B"};
|
string[] notes = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
||||||
string result = notes[tone % 12];
|
string result = notes[tone % 12];
|
||||||
if (withOctave)
|
if (withOctave)
|
||||||
result += (tone / 12);
|
result += (tone / 12);
|
||||||
@@ -145,11 +150,12 @@ namespace Vocaluxe.Lib.Sound.Record
|
|||||||
return;
|
return;
|
||||||
_PitchTestRun = true;
|
_PitchTestRun = true;
|
||||||
int toneFrom = 0;
|
int toneFrom = 0;
|
||||||
const int toneTo = 47; //B5
|
const int toneTo = -1; // 47; //B5
|
||||||
Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(toneTo));
|
Console.WriteLine("Testing notes " + _ToneToNote(toneFrom) + " - " + _ToneToNote(toneTo));
|
||||||
byte[] data;
|
byte[] data;
|
||||||
byte[] data2 = new byte[2048];
|
byte[] data2 = new byte[2048];
|
||||||
int ok = 0;
|
int ok = 0;
|
||||||
|
int tests = 0;
|
||||||
double angle = 0;
|
double angle = 0;
|
||||||
for (int distort = 0; distort < 10; distort++)
|
for (int distort = 0; distort < 10; distort++)
|
||||||
{
|
{
|
||||||
@@ -167,11 +173,30 @@ namespace Vocaluxe.Lib.Sound.Record
|
|||||||
if (!ToneValid)
|
if (!ToneValid)
|
||||||
CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " not detected (Distortion: " + distort + ")");
|
CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " not detected (Distortion: " + distort + ")");
|
||||||
else if (Tone != tone % 12)
|
else if (Tone != tone % 12)
|
||||||
|
{
|
||||||
CBase.Log.LogDebug("Note " + _ToneToNote(tone) + " wrongly detected as " + _ToneToNote(Tone, false) + " (Distortion: " + distort + ")");
|
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
|
else
|
||||||
ok++;
|
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);
|
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, 5), 44100, ref angle, out data);
|
||||||
Stopwatch sw = new Stopwatch();
|
Stopwatch sw = new Stopwatch();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
@@ -182,13 +207,108 @@ namespace Vocaluxe.Lib.Sound.Record
|
|||||||
AnalyzeBuffer();
|
AnalyzeBuffer();
|
||||||
}
|
}
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
string msg = "Analyser: Errors: " + ((toneTo - toneFrom + 1) * 10 - ok) + "; Speed: " +
|
string msg = "Analyser: Errors: " + (tests - ok) + "/" + tests + "; Speed: " +
|
||||||
(int)(repeats / (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 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 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;
|
||||||
|
while (wavFile.NumSamplesLeft / wavFile.NumChannels > maxSamplesPerBatch)
|
||||||
|
{
|
||||||
|
byte[] samples = wavFile.GetNextSamples16BitAsBytes(maxSamplesPerBatch, 1);
|
||||||
|
samplesRead += samples.Length / 2;
|
||||||
|
int time = samplesRead * 1000 / wavFile.SampleRate;
|
||||||
|
ProcessNewBuffer(samples);
|
||||||
|
AnalyzeBuffer();
|
||||||
|
while (curTimeIndex + 1 < tones.Count && time >= tones[curTimeIndex + 1].Time)
|
||||||
|
{
|
||||||
|
curTimeIndex++;
|
||||||
|
curNote = tones[curTimeIndex].Note;
|
||||||
|
}
|
||||||
|
if (curNote < 0)
|
||||||
|
continue;
|
||||||
|
ct++;
|
||||||
|
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++;
|
||||||
|
int note2 = (int)Math.Round(_Analyzer2.FindNote());
|
||||||
|
if (note2 < 0)
|
||||||
|
CBase.Log.LogDebug("Note2 " + _ToneToNote(curNote) + " at " + time + "ms not detected");
|
||||||
|
else if (note2 % 12 != curNote % 12)
|
||||||
|
CBase.Log.LogDebug("Note2 " + _ToneToNote(curNote) + " at " + time + "ms wrongly detected as " + _ToneToNote(note2));
|
||||||
|
else
|
||||||
|
ok++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error on file " + fileName + ": " + e);
|
||||||
|
}
|
||||||
|
wavFile.Close();
|
||||||
|
}
|
||||||
|
|
||||||
private static short[] _GetDistort(int len, int tone, int type)
|
private static short[] _GetDistort(int len, int tone, int type)
|
||||||
{
|
{
|
||||||
short[] sdata = new short[len];
|
short[] sdata = new short[len];
|
||||||
|
|||||||
@@ -0,0 +1,295 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace VocaluxeLib.Utils
|
||||||
|
{
|
||||||
|
//Simple class to read and write WAV files
|
||||||
|
public class CWavFile
|
||||||
|
{
|
||||||
|
public short NumChannels { get; private set; }
|
||||||
|
public int SampleRate { get; private set; }
|
||||||
|
public short BitsPerSample { get; private set; }
|
||||||
|
public string FileName { get; private set; }
|
||||||
|
//Unless this is true, all fields are to be considered as invalid
|
||||||
|
public int DataSize { get; private set; }
|
||||||
|
//Number of samples (=SampesPerChannel*NumChannels)
|
||||||
|
public int NumSamples
|
||||||
|
{
|
||||||
|
get { return (DataSize / (BitsPerSample / 8)); }
|
||||||
|
}
|
||||||
|
public int NumSamplesLeft { get; private set; }
|
||||||
|
public bool IsOpen { get; private set; }
|
||||||
|
private int _DataPos;
|
||||||
|
private FileStream _File;
|
||||||
|
private bool _IsWritable;
|
||||||
|
|
||||||
|
public CWavFile()
|
||||||
|
{
|
||||||
|
IsOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
~CWavFile()
|
||||||
|
{
|
||||||
|
Debug.Assert(!IsOpen);
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Create(string fileName, short numChannels, int sampleRate, short bitsPerSample)
|
||||||
|
{
|
||||||
|
if (IsOpen)
|
||||||
|
Close();
|
||||||
|
_File = new FileStream(fileName, FileMode.Create);
|
||||||
|
FileName = fileName;
|
||||||
|
NumChannels = numChannels;
|
||||||
|
SampleRate = sampleRate;
|
||||||
|
BitsPerSample = bitsPerSample;
|
||||||
|
DataSize = 0;
|
||||||
|
NumSamplesLeft = 0;
|
||||||
|
IsOpen = true;
|
||||||
|
_IsWritable = true;
|
||||||
|
_WriteHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
if (!IsOpen)
|
||||||
|
return;
|
||||||
|
if (_IsWritable)
|
||||||
|
{
|
||||||
|
BinaryWriter writer = new BinaryWriter(_File);
|
||||||
|
writer.Seek(4, SeekOrigin.Begin);
|
||||||
|
writer.Write((int)_File.Length - 8);
|
||||||
|
writer.Seek(_DataPos - 4, SeekOrigin.Begin);
|
||||||
|
writer.Write(DataSize);
|
||||||
|
}
|
||||||
|
_File.Dispose();
|
||||||
|
_File = null;
|
||||||
|
IsOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Open(string fileName)
|
||||||
|
{
|
||||||
|
if (IsOpen)
|
||||||
|
Close();
|
||||||
|
if (!File.Exists(fileName))
|
||||||
|
return false;
|
||||||
|
FileName = fileName;
|
||||||
|
_File = new FileStream(FileName, FileMode.Open);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BinaryReader reader = new BinaryReader(_File);
|
||||||
|
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "RIFF")
|
||||||
|
return false;
|
||||||
|
reader.ReadInt32(); //Chunksize (file)
|
||||||
|
if (Encoding.ASCII.GetString(reader.ReadBytes(4)) != "WAVE")
|
||||||
|
return false;
|
||||||
|
long curPos = _File.Position;
|
||||||
|
if (_SeekTo(reader, "fmt ") < 0)
|
||||||
|
return false;
|
||||||
|
short audioFormat = reader.ReadInt16();
|
||||||
|
if (audioFormat != 1) //PCM
|
||||||
|
return false;
|
||||||
|
NumChannels = reader.ReadInt16();
|
||||||
|
SampleRate = reader.ReadInt32();
|
||||||
|
int bytesPerSec = reader.ReadInt32();
|
||||||
|
short blockAlign = reader.ReadInt16();
|
||||||
|
BitsPerSample = reader.ReadInt16();
|
||||||
|
if (BitsPerSample != 8 && BitsPerSample != 16)
|
||||||
|
return false;
|
||||||
|
if (blockAlign != NumChannels * (BitsPerSample / 8))
|
||||||
|
return false;
|
||||||
|
if (bytesPerSec != blockAlign * SampleRate)
|
||||||
|
return false;
|
||||||
|
_File.Position = curPos;
|
||||||
|
DataSize = _SeekTo(reader, "data");
|
||||||
|
if (DataSize <= 0)
|
||||||
|
return false;
|
||||||
|
_DataPos = (int)_File.Position;
|
||||||
|
DataSize = Math.Min(DataSize, (int)_File.Length - _DataPos);
|
||||||
|
NumSamplesLeft = NumSamples;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
_File.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
IsOpen = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an array of numSamples samples per channel for the selected channel(s)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="numSamples">Samples per channel</param>
|
||||||
|
/// <param name="channel">Channel for which to get the samples (0=all)</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public byte[] GetNextSamples8Bit(int numSamples, int channel = 0)
|
||||||
|
{
|
||||||
|
if (channel < 0 || channel > NumChannels || BitsPerSample != 8)
|
||||||
|
return null;
|
||||||
|
int samplesPerBlock = (channel == 0 ? NumChannels : 1);
|
||||||
|
if (NumSamplesLeft < numSamples * samplesPerBlock)
|
||||||
|
return null;
|
||||||
|
byte[] result;
|
||||||
|
BinaryReader reader = new BinaryReader(_File);
|
||||||
|
if (channel == 0 || NumChannels == 1)
|
||||||
|
result = reader.ReadBytes(numSamples * samplesPerBlock);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = new byte[numSamples];
|
||||||
|
for (int i = 0; i < numSamples; i++)
|
||||||
|
{
|
||||||
|
_File.Seek(channel - 1, SeekOrigin.Current);
|
||||||
|
result[i] = reader.ReadByte();
|
||||||
|
_File.Seek(NumChannels - channel, SeekOrigin.Current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumSamplesLeft -= numSamples * samplesPerBlock;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an array of numSamples samples per channel for the selected channel(s)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="numSamples">Samples per channel</param>
|
||||||
|
/// <param name="channel">Channel for which to get the samples (0=all)</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public short[] GetNextSamples16Bit(int numSamples, int channel = 0)
|
||||||
|
{
|
||||||
|
if (channel < 0 || channel > NumChannels || BitsPerSample != 16)
|
||||||
|
return null;
|
||||||
|
int samplesPerBlock = (channel == 0 ? NumChannels : 1);
|
||||||
|
if (NumSamplesLeft < numSamples * samplesPerBlock)
|
||||||
|
return null;
|
||||||
|
short[] result;
|
||||||
|
BinaryReader reader = new BinaryReader(_File);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (channel == 0 || NumChannels == 1)
|
||||||
|
{
|
||||||
|
result = new short[numSamples * NumChannels];
|
||||||
|
for (int i = 0; i < numSamples * NumChannels; i++)
|
||||||
|
result[i] = reader.ReadInt16();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = new short[numSamples];
|
||||||
|
for (int i = 0; i < numSamples; i++)
|
||||||
|
{
|
||||||
|
_File.Seek((channel - 1) * 2, SeekOrigin.Current);
|
||||||
|
result[i] = reader.ReadInt16();
|
||||||
|
_File.Seek((NumChannels - channel) * 2, SeekOrigin.Current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumSamplesLeft -= numSamples * samplesPerBlock;
|
||||||
|
}
|
||||||
|
catch (EndOfStreamException e)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: " + e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetNextSamples16BitAsBytes(int numSamples, int channel = 0)
|
||||||
|
{
|
||||||
|
short[] samples = GetNextSamples16Bit(numSamples, channel);
|
||||||
|
if (samples == null)
|
||||||
|
return null;
|
||||||
|
byte[] samplesByte = new byte[samples.Length * 2];
|
||||||
|
Buffer.BlockCopy(samples, 0, samplesByte, 0, samplesByte.Length);
|
||||||
|
return samplesByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool WriteSamples(byte[] samples, int channel = 0)
|
||||||
|
{
|
||||||
|
if (!_IsWritable || channel < 0 || channel > NumChannels || BitsPerSample != 8)
|
||||||
|
return false;
|
||||||
|
BinaryWriter writer = new BinaryWriter(_File);
|
||||||
|
if (channel == 0 || NumChannels == 1)
|
||||||
|
writer.Write(samples);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (byte sample in samples)
|
||||||
|
{
|
||||||
|
_File.Seek(channel - 1, SeekOrigin.Current);
|
||||||
|
writer.Write(sample);
|
||||||
|
_File.Seek(NumChannels - channel, SeekOrigin.Current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataSize += samples.Length;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Write16BitSamples(short[] samples, int channel = 0)
|
||||||
|
{
|
||||||
|
if (!_IsWritable || channel < 0 || channel > NumChannels || BitsPerSample != 16)
|
||||||
|
return false;
|
||||||
|
BinaryWriter writer = new BinaryWriter(_File);
|
||||||
|
if (channel == 0 || NumChannels == 1)
|
||||||
|
{
|
||||||
|
foreach (short sample in samples)
|
||||||
|
writer.Write(sample);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (short sample in samples)
|
||||||
|
{
|
||||||
|
_File.Seek((channel - 1) * 2, SeekOrigin.Current);
|
||||||
|
writer.Write(sample);
|
||||||
|
_File.Seek((NumChannels - channel) * 2, SeekOrigin.Current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataSize += samples.Length * 2;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Write16BitSamples(byte[] samples, int channel = 0)
|
||||||
|
{
|
||||||
|
if (samples.Length == 0)
|
||||||
|
return true;
|
||||||
|
short[] samplesShort = new short[samples.Length / 2];
|
||||||
|
Buffer.BlockCopy(samples, 0, samplesShort, 0, samples.Length);
|
||||||
|
return Write16BitSamples(samplesShort, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _WriteHeader()
|
||||||
|
{
|
||||||
|
if (!IsOpen || !_IsWritable)
|
||||||
|
return;
|
||||||
|
BinaryWriter writer = new BinaryWriter(_File);
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||||
|
writer.Write(0);
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("fmt "));
|
||||||
|
writer.Write(16);
|
||||||
|
writer.Write((short)1); // PCM
|
||||||
|
writer.Write(NumChannels); // Channels
|
||||||
|
writer.Write(SampleRate); // Sample rate
|
||||||
|
writer.Write(SampleRate * NumChannels * (BitsPerSample / 8)); // Average bytes per second
|
||||||
|
writer.Write((short)((BitsPerSample / 8) * NumChannels)); // block align
|
||||||
|
writer.Write(BitsPerSample); // bits per sample
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("data"));
|
||||||
|
writer.Write(DataSize);
|
||||||
|
_DataPos = (int)_File.Position;
|
||||||
|
writer.Seek(4, SeekOrigin.Begin);
|
||||||
|
writer.Write((int)writer.BaseStream.Length);
|
||||||
|
writer.Seek(_DataPos, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int _SeekTo(BinaryReader reader, string chunkId)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
string curChunkId = Encoding.ASCII.GetString(reader.ReadBytes(4));
|
||||||
|
int chunkSize = reader.ReadInt32();
|
||||||
|
if (curChunkId == chunkId)
|
||||||
|
return chunkSize;
|
||||||
|
reader.BaseStream.Seek(chunkSize, SeekOrigin.Current);
|
||||||
|
} while (reader.BaseStream.Position < reader.BaseStream.Length);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -188,6 +188,7 @@
|
|||||||
<Compile Include="PartyModes\CScreenPartyDummy.cs" />
|
<Compile Include="PartyModes\CScreenPartyDummy.cs" />
|
||||||
<Compile Include="PartyModes\IPartyMode.cs" />
|
<Compile Include="PartyModes\IPartyMode.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Utils\CWavFile.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
Reference in new issue
Block a user