Merge fast detector with analyzer and use it in CBuffer
This commit is contained in:
1 parent
b1bca37bf8
commit
c781f5427f
11 files changed
+141
-314
No files matched your search
@@ -9,7 +9,7 @@ float* short2FloatArray(short* in, size_t len){
|
||||
|
||||
float* result = static_cast<float*>(malloc(len * sizeof(float)));
|
||||
float* curOut = result;
|
||||
for(int i=0; i<len; i++){
|
||||
for(size_t i=0; i<len; i++){
|
||||
*curOut = *in / maxShort;
|
||||
curOut++;
|
||||
in++;
|
||||
@@ -22,7 +22,7 @@ double* short2DoubleArray(short* in, size_t len, bool scale = true){
|
||||
|
||||
double* result = static_cast<double*>(malloc(len * sizeof(double)));
|
||||
double* curOut = result;
|
||||
for(int i=0; i<len; i++){
|
||||
for(size_t i=0; i<len; i++){
|
||||
*curOut = *in;
|
||||
if(scale)
|
||||
*curOut /= maxShort;
|
||||
|
||||
@@ -131,8 +131,8 @@
|
||||
<PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
<FloatingPointModel>Fast</FloatingPointModel>
|
||||
<OmitFramePointers>false</OmitFramePointers>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
|
||||
@@ -2,6 +2,22 @@
|
||||
#include "pitchTracker.h"
|
||||
#include "Helper.h"
|
||||
|
||||
AnalyzerExt::AnalyzerExt(double baseToneFrequency, int minHalfTone, int maxHalfTone, unsigned step): Analyzer(44100., "", step){
|
||||
PitchTracker::Init(baseToneFrequency, minHalfTone, maxHalfTone);
|
||||
}
|
||||
|
||||
AnalyzerExt::~AnalyzerExt(){
|
||||
PitchTracker::DeInit();
|
||||
}
|
||||
|
||||
int AnalyzerExt::GetNoteFast(float* weights){
|
||||
size_t size = m_fastAnalysisBuf.size();
|
||||
if(size > m_SampleCt)
|
||||
m_fastAnalysisBuf.pop(size - m_SampleCt);
|
||||
m_fastAnalysisBuf.read(m_AnalysisBuf, m_AnalysisBuf + m_SampleCt);
|
||||
return PitchTracker::GetTone(m_AnalysisBuf, m_SampleCt, weights);
|
||||
}
|
||||
|
||||
void PtFast_Init(double baseToneFrequency, int minHalfTone, int maxHalfTone){
|
||||
PitchTracker::Init(baseToneFrequency, minHalfTone, maxHalfTone);
|
||||
}
|
||||
@@ -19,22 +35,22 @@ int PtFast_GetTone(short* samples, int sampleCt, float* weights){
|
||||
return result;
|
||||
}
|
||||
|
||||
Analyzer* Analyzer_Create(double rate, unsigned step){
|
||||
return new Analyzer(rate, "", step);
|
||||
AnalyzerExt* Analyzer_Create(double baseToneFrequency, int minHalfTone, int maxHalfTone, unsigned step){
|
||||
return new AnalyzerExt(baseToneFrequency, minHalfTone, maxHalfTone, step);
|
||||
}
|
||||
|
||||
void Analyzer_Free(Analyzer* analyzer){
|
||||
void Analyzer_Free(AnalyzerExt* analyzer){
|
||||
if(analyzer)
|
||||
delete analyzer;
|
||||
}
|
||||
|
||||
void Analyzer_InputFloat(Analyzer* analyzer, float* data, int sampleCt){
|
||||
void Analyzer_InputFloat(AnalyzerExt* analyzer, float* data, int sampleCt){
|
||||
if(sampleCt == 0 || !analyzer)
|
||||
return;
|
||||
analyzer->input(data, data + sampleCt);
|
||||
}
|
||||
|
||||
void Analyzer_InputShort(Analyzer* analyzer, short* data, int sampleCt){
|
||||
void Analyzer_InputShort(AnalyzerExt* analyzer, short* data, int sampleCt){
|
||||
if(sampleCt == 0 || !analyzer)
|
||||
return;
|
||||
float* dataFloat = short2FloatArray(data, sampleCt);
|
||||
@@ -42,7 +58,7 @@ void Analyzer_InputShort(Analyzer* analyzer, short* data, int sampleCt){
|
||||
freeFloatArray(dataFloat);
|
||||
}
|
||||
|
||||
void Analyzer_InputByte(Analyzer* analyzer, char* data, int sampleCt){
|
||||
void Analyzer_InputByte(AnalyzerExt* analyzer, char* data, int sampleCt){
|
||||
if(sampleCt == 0 || !analyzer)
|
||||
return;
|
||||
float* dataFloat = short2FloatArray(static_cast<short*>(static_cast<void*>(data)), sampleCt);
|
||||
@@ -50,26 +66,32 @@ void Analyzer_InputByte(Analyzer* analyzer, char* data, int sampleCt){
|
||||
freeFloatArray(dataFloat);
|
||||
}
|
||||
|
||||
void Analyzer_Process(Analyzer* analyzer){
|
||||
void Analyzer_Process(AnalyzerExt* analyzer){
|
||||
if(!analyzer)
|
||||
return;
|
||||
analyzer->process();
|
||||
}
|
||||
|
||||
double Analyzer_GetPeak(Analyzer* analyzer){
|
||||
double Analyzer_GetPeak(AnalyzerExt* analyzer){
|
||||
if(!analyzer)
|
||||
return -999;
|
||||
return analyzer->getPeak();
|
||||
}
|
||||
|
||||
double Analyzer_FindNote(Analyzer* analyzer, double minFreq, double maxFreq){
|
||||
double Analyzer_FindNote(AnalyzerExt* analyzer, double minFreq, double maxFreq){
|
||||
if(!analyzer)
|
||||
return -1;
|
||||
const Tone* tone = analyzer->findTone(minFreq, maxFreq);
|
||||
return (tone == NULL) ? -1 : ToneToNote(tone);
|
||||
}
|
||||
|
||||
bool Analyzer_OutputFloat(Analyzer* analyzer, float* data, int sampleCt, float rate){
|
||||
int Analyzer_GetNoteFast(AnalyzerExt* analyzer, float* weights){
|
||||
if(!analyzer)
|
||||
return -1;
|
||||
return analyzer->GetNoteFast(weights);
|
||||
}
|
||||
|
||||
bool Analyzer_OutputFloat(AnalyzerExt* analyzer, float* data, int sampleCt, float rate){
|
||||
if(!analyzer)
|
||||
return false;
|
||||
return analyzer->output(data, data + sampleCt, rate);
|
||||
|
||||
+26
-10
@@ -4,19 +4,35 @@
|
||||
|
||||
#define DllExport extern "C" __declspec(dllexport)
|
||||
|
||||
class AnalyzerExt: public Analyzer{
|
||||
public:
|
||||
AnalyzerExt(double baseToneFrequency, int minHalfTone, int maxHalfTone, unsigned step = 200);
|
||||
~AnalyzerExt();
|
||||
template <typename InIt> void input(InIt begin, InIt end){
|
||||
m_fastAnalysisBuf.insert(begin, end);
|
||||
Analyzer::input(begin, end);
|
||||
}
|
||||
int GetNoteFast(float* weights);
|
||||
private:
|
||||
constexpr static size_t m_SampleCt = 4096;
|
||||
RingBuffer<m_SampleCt * 2> m_fastAnalysisBuf; // Buffer used for fast analysis instead of FFT
|
||||
float m_AnalysisBuf[4096];
|
||||
};
|
||||
|
||||
DllExport void PtFast_Init(double baseToneFrequency, int minHalfTone, int maxHalfTone);
|
||||
DllExport void PtFast_DeInit();
|
||||
DllExport int PtFast_GetTone(short* samples, int sampleCt, float* weights);
|
||||
|
||||
DllExport Analyzer* Analyzer_Create(double rate, unsigned step);
|
||||
DllExport void Analyzer_Free(Analyzer* analyzer);
|
||||
DllExport void Analyzer_InputFloat(Analyzer* analyzer, float* data, int sampleCt);
|
||||
DllExport void Analyzer_InputShort(Analyzer* analyzer, short* data, int sampleCt);
|
||||
DllExport void Analyzer_InputByte(Analyzer* analyzer, char* data, int sampleCt);
|
||||
DllExport void Analyzer_Process(Analyzer* analyzer);
|
||||
DllExport double Analyzer_GetPeak(Analyzer* analyzer);
|
||||
DllExport double Analyzer_FindNote(Analyzer* analyzer, double minFreq, double maxFreq);
|
||||
DllExport bool Analyzer_OutputFloat(Analyzer* analyzer, float* data, int sampleCt, float rate);
|
||||
DllExport AnalyzerExt* Analyzer_Create(double baseToneFrequency, int minHalfTone, int maxHalfTone, unsigned step);
|
||||
DllExport void Analyzer_Free(AnalyzerExt* analyzer);
|
||||
DllExport void Analyzer_InputFloat(AnalyzerExt* analyzer, float* data, int sampleCt);
|
||||
DllExport void Analyzer_InputShort(AnalyzerExt* analyzer, short* data, int sampleCt);
|
||||
DllExport void Analyzer_InputByte(AnalyzerExt* analyzer, char* data, int sampleCt);
|
||||
DllExport void Analyzer_Process(AnalyzerExt* analyzer);
|
||||
DllExport double Analyzer_GetPeak(AnalyzerExt* analyzer);
|
||||
DllExport int Analyzer_GetNoteFast(AnalyzerExt* analyzer, float* weights);
|
||||
DllExport double Analyzer_FindNote(AnalyzerExt* analyzer, double minFreq, double maxFreq);
|
||||
DllExport bool Analyzer_OutputFloat(AnalyzerExt* analyzer, float* data, int sampleCt, float rate);
|
||||
|
||||
/*
|
||||
public ref class CTone {
|
||||
@@ -59,7 +75,7 @@ public:
|
||||
String^ GetId();
|
||||
|
||||
private:
|
||||
Analyzer* m_analyzer;
|
||||
AnalyzerExt* m_analyzer;
|
||||
String^ m_id;
|
||||
};
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ Analyzer::Analyzer(double rate, std::string id, unsigned step):
|
||||
|
||||
bool Analyzer::output(float* begin, float* end, double rate) {
|
||||
constexpr unsigned a = 2;
|
||||
const unsigned size = m_passthrough.size();
|
||||
const size_t size = m_passthrough.size();
|
||||
const unsigned out = static_cast<unsigned>((end - begin) / 2 /* stereo */);
|
||||
if (out == 0) return false;
|
||||
const unsigned in = static_cast<unsigned>(m_resampleFactor * (m_rate / rate) * out + 2 * a /* lanczos kernel */ + 5 /* safety margin for rounding errors */);
|
||||
@@ -72,7 +72,7 @@ bool Analyzer::output(float* begin, float* end, double rate) {
|
||||
m_resamplePos -= num;
|
||||
if (size > 3000) {
|
||||
// Reset
|
||||
m_passthrough.pop(m_passthrough.size() - 700);
|
||||
m_passthrough.pop(size - 700);
|
||||
m_resampleFactor = 1.0;
|
||||
} else {
|
||||
m_passthrough.pop(num);
|
||||
@@ -133,8 +133,8 @@ void Analyzer::calcTones() {
|
||||
const size_t kMax = std::min(FFT_N / 2, size_t(FFT_MAXFREQ / freqPerBin));
|
||||
std::vector<Peak> peaks(kMax + 1); // One extra to simplify loops
|
||||
for (size_t k = 1; k <= kMax; ++k) {
|
||||
float magnitude = std::abs(m_fft[k]);
|
||||
float phase = std::arg(m_fft[k]);
|
||||
double magnitude = std::abs(m_fft[k]);
|
||||
double phase = std::arg(m_fft[k]);
|
||||
// process phase difference
|
||||
double delta = phase - m_fftLastPhase[k];
|
||||
m_fftLastPhase[k] = phase;
|
||||
|
||||
+11
-11
@@ -34,14 +34,14 @@ static const unsigned FFT_P = 10;
|
||||
static const std::size_t FFT_N = 1 << FFT_P;
|
||||
|
||||
/// Lock-free ring buffer. Discards oldest data on overflow (not strictly thread-safe).
|
||||
template <unsigned SIZE> class RingBuffer {
|
||||
template <size_t SIZE> class RingBuffer {
|
||||
public:
|
||||
constexpr static unsigned capacity = SIZE;
|
||||
constexpr static size_t capacity = SIZE;
|
||||
RingBuffer(): m_read(), m_write() {} ///< Initialize empty buffer
|
||||
template <typename InIt> void insert(InIt begin, InIt end) {
|
||||
unsigned r = m_read; // The read position
|
||||
unsigned w = m_write; // The write position
|
||||
bool overflow = (end - begin >= SIZE - size());
|
||||
size_t r = m_read; // The read position
|
||||
size_t w = m_write; // The write position
|
||||
bool overflow = (static_cast<size_t>(end - begin) >= SIZE - size());
|
||||
while (begin != end) {
|
||||
m_buf[w] = *begin++; // Copy sample
|
||||
w = modulo(w + 1); // Update cursor
|
||||
@@ -51,17 +51,17 @@ public:
|
||||
}
|
||||
/// Read data from current position if there is enough data to fill the range (otherwise return false). Does not move read pointer.
|
||||
template <typename OutIt> bool read(OutIt begin, OutIt end) {
|
||||
unsigned r = m_read;
|
||||
if (size() <= end - begin) return false; // Not enough audio available
|
||||
size_t r = m_read;
|
||||
if (size() <= static_cast<size_t>(end - begin)) return false; // Not enough audio available
|
||||
while (begin != end) *begin++ = m_buf[r++ % SIZE]; // Copy audio to output iterator
|
||||
return true;
|
||||
}
|
||||
void pop(unsigned n) { m_read = modulo(m_read + n); } ///< Move reading pointer forward.
|
||||
unsigned size() const { return modulo(m_write - m_read); }
|
||||
void pop(size_t n) { m_read = modulo(m_read + n); } ///< Move reading pointer forward.
|
||||
size_t size() const { return modulo(m_write - m_read); }
|
||||
private:
|
||||
static unsigned modulo(int idx) { return (SIZE + idx) % SIZE; } ///< Modulo operation with proper rounding (handles slightly "negative" idx as well)
|
||||
static size_t modulo(size_t idx) { return idx % SIZE; } ///< Modulo operation with proper rounding (handles slightly "negative" idx as well)
|
||||
float m_buf[SIZE];
|
||||
volatile unsigned m_read, m_write; ///< The indices of the next read/write operations. read == write implies that buffer is empty.
|
||||
volatile size_t m_read, m_write; ///< The indices of the next read/write operations. read == write implies that buffer is empty.
|
||||
};
|
||||
|
||||
/// analyzer class
|
||||
|
||||
@@ -35,15 +35,19 @@ namespace PitchTracker{
|
||||
Log=Callback;
|
||||
}
|
||||
|
||||
double *SamplesPerPeriodPerTone = NULL;
|
||||
int MinHalfTone = 0, MaxHalfTone = 38;
|
||||
const double HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
|
||||
static double *SamplesPerPeriodPerTone = NULL;
|
||||
static double BaseToneFrequency = -1;
|
||||
static int MinHalfTone = -1, MaxHalfTone = -1;
|
||||
static const double HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
|
||||
|
||||
void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone){
|
||||
if(SamplesPerPeriodPerTone && abs(baseToneFrequency - BaseToneFrequency) < 1. && minHalfTone == MinHalfTone && maxHalfTone == MaxHalfTone)
|
||||
return;
|
||||
BaseToneFrequency = baseToneFrequency;
|
||||
MinHalfTone = minHalfTone;
|
||||
MaxHalfTone = maxHalfTone;
|
||||
DeInit();
|
||||
if(maxHalfTone <= 0)
|
||||
if(minHalfTone < 0 || maxHalfTone < minHalfTone)
|
||||
return;
|
||||
SamplesPerPeriodPerTone = (double*) malloc((MaxHalfTone + 1) * sizeof(double));
|
||||
//Init Array to avoid costly calculations
|
||||
@@ -61,13 +65,14 @@ namespace PitchTracker{
|
||||
}
|
||||
}
|
||||
|
||||
static double _AnalyzeToneFunc(double *samples, int sampleCt, int toneIndex){
|
||||
template<typename T>
|
||||
static T _AnalyzeToneFunc(T *samples, int sampleCt, int toneIndex){
|
||||
double samplesPerPeriodD = SamplesPerPeriodPerTone[toneIndex]; // samples in one period
|
||||
int samplesPerPeriod = (int)samplesPerPeriodD;
|
||||
double fHigh = samplesPerPeriodD - samplesPerPeriod;
|
||||
double fLow = 1.0 - fHigh;
|
||||
int samplesPerPeriod = static_cast<int>(samplesPerPeriodD);
|
||||
T fHigh = static_cast<T>(samplesPerPeriodD - samplesPerPeriod);
|
||||
T fLow = static_cast<T>(1.0 - fHigh);
|
||||
|
||||
double accumDist = 0; // accumulated distances
|
||||
T accumDist = 0; // accumulated distances
|
||||
|
||||
// compare correlating samples
|
||||
int sampleIndex = 0; // index of sample to analyze
|
||||
@@ -75,28 +80,30 @@ namespace PitchTracker{
|
||||
for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex + 1 < sampleCt; correlatingSampleIndex++, sampleIndex++)
|
||||
{
|
||||
// calc distance to corresponding sample in next period (lower means more distant)
|
||||
double dist = samples[sampleIndex] * (samples[correlatingSampleIndex] * fLow + samples[correlatingSampleIndex + 1] * fHigh);
|
||||
T dist = samples[sampleIndex] * (samples[correlatingSampleIndex] * fLow + samples[correlatingSampleIndex + 1] * fHigh);
|
||||
accumDist += dist;
|
||||
}
|
||||
|
||||
//Using _AnalysisBufLen here makes it return correct values among all analyzed frequencies
|
||||
double scaleValue = sampleCt; // Divide also by Int16.MaxValue^2
|
||||
return accumDist / scaleValue;
|
||||
//Using sampleCt here makes it return correct values among all analyzed frequencies
|
||||
return accumDist / sampleCt;
|
||||
}
|
||||
|
||||
int GetTone(double *samples, int sampleCt, float *weights, bool scale){
|
||||
double maxVolume = 0;
|
||||
template<typename T>
|
||||
int GetTone(T *samples, int sampleCt, float *weights, bool scale){
|
||||
if(!SamplesPerPeriodPerTone)
|
||||
return -1;
|
||||
T maxVolume = 0;
|
||||
for(int i = 0; i < sampleCt; i++){
|
||||
double vol = abs(samples[i]);
|
||||
T vol = abs(samples[i]);
|
||||
if(vol > maxVolume)
|
||||
maxVolume = vol;
|
||||
}
|
||||
double maxWeight = -1;
|
||||
double minWeight = 1;
|
||||
T maxWeight = -1;
|
||||
T minWeight = 1;
|
||||
int maxTone = -1;
|
||||
for (int toneIndex = MinHalfTone; toneIndex <= MaxHalfTone; toneIndex++)
|
||||
{
|
||||
double curWeight = _AnalyzeToneFunc(samples, sampleCt, toneIndex) / maxVolume;
|
||||
T curWeight = _AnalyzeToneFunc<T>(samples, sampleCt, toneIndex) / maxVolume;
|
||||
if(scale)
|
||||
curWeight /= 32768.0; //maximum abs value of a short, would actually need to scale by MaxValue^2 but we already scaled by maxVolume
|
||||
|
||||
@@ -109,7 +116,7 @@ namespace PitchTracker{
|
||||
if (curWeight < minWeight)
|
||||
minWeight = curWeight;
|
||||
|
||||
weights[toneIndex - MinHalfTone] = (float) curWeight;
|
||||
weights[toneIndex - MinHalfTone] = static_cast<float>(curWeight);
|
||||
}
|
||||
|
||||
if(maxWeight - minWeight > 0.01){
|
||||
@@ -117,4 +124,7 @@ namespace PitchTracker{
|
||||
}else return -1;
|
||||
}
|
||||
|
||||
template int GetTone(float *samples, int sampleCt, float *weights, bool scale);
|
||||
template int GetTone(double *samples, int sampleCt, float *weights, bool scale);
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace PitchTracker{
|
||||
void Init(double baseToneFrequency, int minHalfTone, int maxHalfTone);
|
||||
void DeInit();
|
||||
int GetTone(double *samples, int sampleCt, float *weights, bool scale = false);
|
||||
template<typename T> int GetTone(T *samples, int sampleCt, float *weights, bool scale = false);
|
||||
|
||||
typedef void (__stdcall * LogCallback)(const char* text);
|
||||
static LogCallback Log;
|
||||
|
||||
@@ -7,19 +7,19 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
{
|
||||
#region Imports
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern IntPtr Analyzer_Create(double rate, uint step);
|
||||
private static extern IntPtr Analyzer_Create(double baseToneFrequency, int minHalfTone, int maxHalfTone, uint step = 200);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void Analyzer_Free(IntPtr analyzer);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void Analyzer_InputFloat(IntPtr analyzer, float[] data, int sampleCt);
|
||||
private static extern void Analyzer_InputFloat(IntPtr analyzer, [In] float[] data, int sampleCt);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void Analyzer_InputShort(IntPtr analyzer, short[] data, int sampleCt);
|
||||
private static extern void Analyzer_InputShort(IntPtr analyzer, [In] short[] data, int sampleCt);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void Analyzer_InputByte(IntPtr analyzer, byte[] data, int sampleCt);
|
||||
private static extern void Analyzer_InputByte(IntPtr analyzer, [In] byte[] data, int sampleCt);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void Analyzer_Process(IntPtr analyzer);
|
||||
@@ -27,6 +27,9 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern double Analyzer_GetPeak(IntPtr analyzer);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern double Analyzer_GetNoteFast(IntPtr analyzer, [Out] float[] weights);
|
||||
|
||||
[DllImport("PitchTracker.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern double Analyzer_FindNote(IntPtr analyzer, double minFreq, double maxFreq);
|
||||
|
||||
@@ -36,9 +39,9 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
|
||||
private IntPtr _Instance;
|
||||
|
||||
public CAnalyzer(double rate = 44100.0, uint step = 200)
|
||||
public CAnalyzer(double baseToneFrequency, int minHalfTone, int maxHalfTone, uint step = 200)
|
||||
{
|
||||
_Instance = Analyzer_Create(rate, step);
|
||||
_Instance = Analyzer_Create(baseToneFrequency, minHalfTone, maxHalfTone, step);
|
||||
}
|
||||
|
||||
~CAnalyzer()
|
||||
@@ -76,6 +79,11 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
return Analyzer_FindNote(_Instance, minFreq, maxFreq);
|
||||
}
|
||||
|
||||
public double GetNoteFast(float[] weights)
|
||||
{
|
||||
return Analyzer_GetNoteFast(_Instance, weights);
|
||||
}
|
||||
|
||||
public bool Output(float[] data, float rate)
|
||||
{
|
||||
return Analyzer_OutputFloat(_Instance, data, data.Length, rate);
|
||||
@@ -106,7 +114,7 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
public static extern void DeInit();
|
||||
|
||||
[DllImport("PitchTracker.dll", EntryPoint = "PtFast_GetTone", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern int PtFast_GetTone(short[] samples, int sampleCt, float[] weights);
|
||||
private static extern int PtFast_GetTone([In] short[] samples, int sampleCt, [Out] float[] weights);
|
||||
#endregion
|
||||
|
||||
public static int GetTone(short[] samples, float[] weights)
|
||||
|
||||
@@ -15,16 +15,12 @@
|
||||
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
|
||||
#endregion
|
||||
|
||||
//#define TEST_PITCH
|
||||
#define USE_NATIVE_DETECTION
|
||||
//If defined, it uses a library with native code that speeds up detection by a factor of 10
|
||||
#define TEST_PITCH
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Diagnostics;
|
||||
|
||||
#if TEST_PITCH
|
||||
using System.Windows.Forms;
|
||||
using System.Diagnostics;
|
||||
using VocaluxeLib;
|
||||
|
||||
#endif
|
||||
@@ -33,69 +29,23 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
{
|
||||
class CBuffer : IDisposable
|
||||
{
|
||||
private static int _InitCount;
|
||||
//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)
|
||||
#if TEST_PITCH
|
||||
private const double _HalftoneBase = 1.05946309436; // 2^(1/12) -> HalftoneBase^12 = 2 (one octave)
|
||||
#endif
|
||||
private const int _HalfToneMin = 0; // C2
|
||||
private const int _HalfToneMax = 38; //45; // inclusive, 38=D5; 45=A5
|
||||
public const int NumHalfTones = _HalfToneMax - _HalfToneMin + 1; //Number of halftones to analyze
|
||||
private CAnalyzer _Analyzer = new CAnalyzer(_BaseToneFreq, _HalfToneMin, _HalfToneMax);
|
||||
|
||||
private const int _AnalysisBufLen = 4096;
|
||||
private const int _AnalysisByteBufLen = _AnalysisBufLen * 2;
|
||||
|
||||
private readonly short[] _AnalysisBuffer = new short[_AnalysisBufLen];
|
||||
private readonly byte[] _AnalysisByteBuffer = new byte[_AnalysisByteBufLen]; //tmp buffer (stream->tmpBuffer->(Int16)AnalysisBuffer)
|
||||
|
||||
private readonly float[] _TmpWeights = new float[NumHalfTones]; //tmp buffer for weights (gets copied to ToneWeight when checked)
|
||||
#if !USE_NATIVE_DETECTION
|
||||
//Precalculated table holding sample# per period for each tone (actually we may not use the lower few entries, but keep it to avoid index modification
|
||||
private static readonly double[] _SamplesPerPeriodPerTone = new double[_HalfToneMax + 1];
|
||||
|
||||
public enum EAnalyzeFunction
|
||||
{
|
||||
AutoCorrelation, //Faster, more resistent to noise, good value for HalfToneMax=38 (higher->wrong results in test case, WHY?) with 38 no wrong values in test case
|
||||
Amdf //Average magnitude difference, slower, detects everything in range -> HalfToneMax=45
|
||||
}
|
||||
private delegate double DAnalyzeTone(int tone);
|
||||
|
||||
// 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;
|
||||
private double _MinWeightDiff;
|
||||
// ReSharper restore FieldCanBeMadeReadOnly.Local
|
||||
#else
|
||||
private readonly CAnalyzer _Analyzer = new CAnalyzer();
|
||||
#endif
|
||||
private double _MaxVolume;
|
||||
private bool _NewSamples;
|
||||
|
||||
private MemoryStream _Stream = new MemoryStream(); // full buffer
|
||||
|
||||
public CBuffer()
|
||||
{
|
||||
_Init();
|
||||
MinVolume = 0.02f;
|
||||
ToneWeigth = new float[NumHalfTones];
|
||||
ToneWeigths = new float[NumHalfTones];
|
||||
Reset();
|
||||
#if !USE_NATIVE_DETECTION
|
||||
if (AnalyzeFunction == EAnalyzeFunction.Amdf)
|
||||
{
|
||||
_AnalyzeToneFunc = _GetAmdf;
|
||||
_MinWeightDiff = 0.01;
|
||||
}
|
||||
else if (AnalyzeFunction == EAnalyzeFunction.AutoCorrelation)
|
||||
{
|
||||
_AnalyzeToneFunc = _GetAutoCorrelation;
|
||||
_MinWeightDiff = 0.0001;
|
||||
}
|
||||
#endif
|
||||
#if TEST_PITCH
|
||||
_TestPitchDetection();
|
||||
#endif
|
||||
@@ -106,23 +56,6 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
_Dispose();
|
||||
}
|
||||
|
||||
private static void _Init()
|
||||
{
|
||||
_InitCount++;
|
||||
if (_InitCount > 1)
|
||||
return; //Do init only once
|
||||
#if USE_NATIVE_DETECTION
|
||||
CFastAnalyzer.Init(_BaseToneFreq, _HalfToneMin, _HalfToneMax);
|
||||
#else
|
||||
//Init Array to avoid costly calculations
|
||||
for (int toneIndex = 0; toneIndex <= _HalfToneMax; toneIndex++)
|
||||
{
|
||||
double freq = _BaseToneFreq * Math.Pow(_HalftoneBase, toneIndex);
|
||||
_SamplesPerPeriodPerTone[toneIndex] = 44100.0 / freq; // samples in one period
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public int ToneAbs { get; private set; }
|
||||
|
||||
public int Tone { get; set; }
|
||||
@@ -134,7 +67,7 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
|
||||
public bool ToneValid { get; private set; }
|
||||
|
||||
public float[] ToneWeigth { get; private set; }
|
||||
public float[] ToneWeigths { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum volume for a tone to be valid
|
||||
@@ -143,21 +76,11 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (_Stream)
|
||||
{
|
||||
_Stream.SetLength(0L);
|
||||
_NewSamples = false;
|
||||
}
|
||||
ToneValid = false;
|
||||
ToneAbs = 0;
|
||||
Tone = 0;
|
||||
for (int i = 0; i < ToneWeigth.Length; i++)
|
||||
ToneWeigth[i] = 0f;
|
||||
}
|
||||
|
||||
private void _Add(byte[] bytes)
|
||||
{
|
||||
_Stream.Write(bytes, 0, bytes.Length);
|
||||
for (int i = 0; i < ToneWeigths.Length; i++)
|
||||
ToneWeigths[i] = 0f;
|
||||
}
|
||||
|
||||
public void ProcessNewBuffer(byte[] buffer)
|
||||
@@ -168,168 +91,24 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
// voice passthrough (send data to playback-device)
|
||||
//if (assigned(fVoiceStream)) then
|
||||
//fVoiceStream.WriteData(Buffer, BufferSize);
|
||||
lock (_Stream)
|
||||
{
|
||||
_Add(buffer);
|
||||
_NewSamples = true;
|
||||
}
|
||||
//_Analyzer.Input(buffer);
|
||||
_Analyzer.Input(buffer);
|
||||
}
|
||||
|
||||
public void AnalyzeBuffer()
|
||||
{
|
||||
if (!_NewSamples)
|
||||
return;
|
||||
|
||||
/*_NewSamples = false;
|
||||
_Analyzer.Process();
|
||||
_MaxVolume = _Analyzer.GetPeak() / 43 + 1;
|
||||
CTone tone = _Analyzer.FindTone();
|
||||
if (tone != null)
|
||||
int tone = (int)Math.Round(_Analyzer.FindNote());
|
||||
if (tone >= 0)
|
||||
{
|
||||
ToneAbs = tone.Note;
|
||||
ToneAbs = tone;
|
||||
Tone = ToneAbs % 12;
|
||||
ToneValid = true;
|
||||
}
|
||||
else
|
||||
ToneValid = false;*/
|
||||
|
||||
lock (_Stream)
|
||||
{
|
||||
if (_Stream.Length >= _AnalysisByteBufLen)
|
||||
{
|
||||
_Stream.Position -= _AnalysisByteBufLen;
|
||||
_Stream.Read(_AnalysisByteBuffer, 0, _AnalysisByteBufLen);
|
||||
}
|
||||
_NewSamples = false;
|
||||
}
|
||||
|
||||
Buffer.BlockCopy(_AnalysisByteBuffer, 0, _AnalysisBuffer, 0, _AnalysisByteBufLen);
|
||||
|
||||
try
|
||||
{
|
||||
// find maximum volume
|
||||
_FindMaxVolume();
|
||||
|
||||
if (MaxVolume >= MinVolume)
|
||||
_AnalyzeTones();
|
||||
else
|
||||
ToneValid = false;
|
||||
}
|
||||
catch (Exception) {}
|
||||
}
|
||||
|
||||
private void _FindMaxVolume()
|
||||
{
|
||||
int maxVolume = 0;
|
||||
for (int i = 0; i < _AnalysisBufLen / 4; i++)
|
||||
{
|
||||
int volume = _AnalysisBuffer[i];
|
||||
if (volume < 0)
|
||||
volume = -volume;
|
||||
if (volume > maxVolume)
|
||||
maxVolume = volume;
|
||||
}
|
||||
_MaxVolume = (double)maxVolume / Int16.MaxValue;
|
||||
}
|
||||
|
||||
private void _AnalyzeTones()
|
||||
{
|
||||
#if USE_NATIVE_DETECTION
|
||||
int maxTone = CFastAnalyzer.GetTone(_AnalysisBuffer, _TmpWeights);
|
||||
if (maxTone >= 0)
|
||||
#else
|
||||
// prepare to analyze
|
||||
double maxWeight = -1.0;
|
||||
double minWeight = 1.0;
|
||||
int maxTone = -1;
|
||||
|
||||
|
||||
// analyze halftones
|
||||
// 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
|
||||
// too few samples -> use a bigger buffer-size
|
||||
for (int toneIndex = _HalfToneMin; toneIndex <= _HalfToneMax; toneIndex++)
|
||||
{
|
||||
double curWeight = _AnalyzeToneFunc(toneIndex);
|
||||
|
||||
if (curWeight > maxWeight)
|
||||
{
|
||||
maxWeight = curWeight;
|
||||
maxTone = toneIndex;
|
||||
}
|
||||
|
||||
if (curWeight < minWeight)
|
||||
minWeight = curWeight;
|
||||
|
||||
_TmpWeights[toneIndex - _HalfToneMin] = (float)curWeight;
|
||||
}
|
||||
|
||||
if (maxWeight - minWeight > _MinWeightDiff)
|
||||
#endif
|
||||
{
|
||||
Array.Copy(_TmpWeights, ToneWeigth, NumHalfTones);
|
||||
|
||||
ToneAbs = maxTone;
|
||||
//Console.WriteLine(maxTone);
|
||||
Tone = maxTone % 12;
|
||||
ToneValid = true;
|
||||
}
|
||||
else
|
||||
ToneValid = false;
|
||||
}
|
||||
|
||||
#if !USE_NATIVE_DETECTION
|
||||
private double _GetAutoCorrelation(int tone)
|
||||
{
|
||||
double samplesPerPeriodD = _SamplesPerPeriodPerTone[tone]; // samples in one period
|
||||
int samplesPerPeriod = (int)samplesPerPeriodD;
|
||||
double fHigh = samplesPerPeriodD - samplesPerPeriod;
|
||||
double fLow = 1.0 - fHigh;
|
||||
|
||||
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 + 1 < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++)
|
||||
{
|
||||
// calc distance to corresponding sample in next period (lower means more distant)
|
||||
double dist = _AnalysisBuffer[sampleIndex] *
|
||||
(_AnalysisBuffer[correlatingSampleIndex] * fLow + _AnalysisBuffer[correlatingSampleIndex + 1] * fHigh);
|
||||
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)
|
||||
{
|
||||
double samplesPerPeriodD = _SamplesPerPeriodPerTone[tone]; // samples in one period
|
||||
int samplesPerPeriod = (int)samplesPerPeriodD;
|
||||
double fHigh = samplesPerPeriodD - samplesPerPeriod;
|
||||
double fLow = 1.0 - fHigh;
|
||||
|
||||
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 + 1 < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++)
|
||||
{
|
||||
// calc distance (correlation: 1-dist/IntMax*2) to corresponding sample in next period (0=equal .. IntMax*2=totally different)
|
||||
//int dist = (int)Math.Abs(_AnalysisBuffer[sampleIndex] - _AnalysisBuffer[correlatingSampleIndex]);
|
||||
double dist = Math.Abs(_AnalysisBuffer[sampleIndex] -
|
||||
(_AnalysisBuffer[correlatingSampleIndex] * fLow + _AnalysisBuffer[correlatingSampleIndex + 1] * fHigh));
|
||||
accumDist += dist;
|
||||
}
|
||||
//Use analyzed sample count here. BufLen yields more errors
|
||||
return 1.0 - accumDist / Int16.MaxValue / sampleIndex;
|
||||
}
|
||||
#endif
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_Dispose();
|
||||
@@ -338,17 +117,11 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
|
||||
private void _Dispose()
|
||||
{
|
||||
if (_Stream != null)
|
||||
if (_Analyzer != null)
|
||||
{
|
||||
_Stream.Dispose();
|
||||
_Stream = null;
|
||||
_Analyzer.Dispose();
|
||||
_Analyzer = null;
|
||||
}
|
||||
#if USE_NATIVE_DETECTION
|
||||
Debug.Assert(_InitCount > 0);
|
||||
_InitCount--;
|
||||
if (_InitCount == 0)
|
||||
CFastAnalyzer.DeInit();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if TEST_PITCH
|
||||
@@ -393,11 +166,9 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
}
|
||||
}
|
||||
_GetSineWave(_BaseToneFreq * Math.Pow(_HalftoneBase, 5), 44100, ref angle, out data);
|
||||
ProcessNewBuffer(data);
|
||||
AnalyzeBuffer();
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
const int repeats = 2000;
|
||||
const int repeats = 1000;
|
||||
for (int i = 0; i < repeats; i++)
|
||||
{
|
||||
ProcessNewBuffer(data);
|
||||
@@ -486,7 +257,7 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
private static void _GetSineWave(double freq, int sampleRate, ref double angle, out byte[] data)
|
||||
{
|
||||
const short max = short.MaxValue;
|
||||
const int len = _AnalysisBufLen; //sampleRate * durationMs / 1000;
|
||||
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);
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace Vocaluxe.Lib.Sound.Record
|
||||
|
||||
public float[] ToneWeigth(int player)
|
||||
{
|
||||
return _Initialized ? _Buffer[player].ToneWeigth : null;
|
||||
return _Initialized ? _Buffer[player].ToneWeigths : null;
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<CRecordDevice> RecordDevices()
|
||||
|
||||
Reference in new issue
Block a user