Hello,
Im trying to do pitch detection of a users singing. Problem is, it keeps returning random results. I've got some code from http://code.google.com/p/yaalp/ which i've converted to C (below). My sample rate is 44100, and data size is 1024. I'm detecting pitch of both a sine wave and mic input. The frequency of the sine wave is 726.0, and its detecting it to be 722.950820 (which im ok with), but its detecting the pitch of the mic as a random number from around 100 to around 1050.
double DetectPitch(short* data)
{
int sampleRate = 44100;
//Create sine wave
short *buffer = malloc(1024*sizeof(short));
double amplitude = 0.25 * 32768; //0.25 * max length of short
double frequency = 726.0;
for (int n = 0; n < 1024; n++)
{
buffer[n] = (short)(amplitude * sin((2 * 3.14159265 * n * frequency) / sampleRate));
}
printf("Pitch from sine wave: %f\n",detectPitchCalculation(buffer, 50.0, 1000.0, 1, 1));
printf("Pitch from mic: %f\n",detectPitchCalculation(data, 50.0, 1000.0, 1, 1));
return 0;
}
// These work by shifting the signal until it seems to correlate with itself.
// In other words if the signal looks very similar to (signal shifted 200 data) than the fundamental period is probably 200 data
// Note that the algorithm only works well when there's only one prominent fundamental.
// This could be optimized by looking at the rate of change to determine a maximum without testing all periods.
double detectPitchCalculation(short* data, double minHz, double maxHz, int nCandidates, int nResolution)
{
//-------------------------1-------------------------//
// note that higher frequency means lower period
int nLowPeriodInSamples = hzToPeriodInSamples(maxHz, 44100);
int nHiPeriodInSamples = hzToPeriodInSamples(minHz, 44100);
if (nHiPeriodInSamples <= nLowPeriodInSamples) printf("Bad range for pitch detection.");
if (1024 < nHiPeriodInSamples) printf("Not enough data.");
double *results = malloc((nHiPeriodInSamples - nLowPeriodInSamples)*sizeof(double));
//-------------------------2-------------------------//
for (int period = nLowPeriodInSamples; period < nHiPeriodInSamples; period += nResolution)
{
double sum = 0;
// for each sample, find correlation. (If they are far apart, small)
for (int i = 0; i < 1024 - period; i++)
sum += data[i] * data[i + period];
double mean = sum / 1024.0;
results[period - nLowPeriodInSamples] = mean;
}
//-------------------------3-------------------------//
// find the best indices
int *bestIndices = findBestCandidates(nCandidates, results, nHiPeriodInSamples - nLowPeriodInSamples - 1); //note findBestCandidates modifies parameter
// convert back to Hz
double *res = malloc(nCandidates*(sizeof(double)));
for (int i=0; i < nCandidates;i++) res[i] = periodInSamplesToHz(bestIndices[i]+nLowPeriodInSamples, 44100);
double pitch2 = res[0];
free(res);
free(results);
return pitch2;
}
/// Finds n "best" values from an array. Returns the indices of the best parts.
/// (One way to do this would be to sort the array, but that could take too long.
/// Warning: Changes the contents of the array!!! Do not use result array afterwards.
int* findBestCandidates(int n, double* inputs,int length)
{
//int length = inputs.Length;
if (length < n) printf("Length of inputs is not long enough.");
int* res = malloc(n*sizeof(int));
double minValue = 0;
for (int c = 0; c < n; c++)
{
// find the highest.
double fBestValue = minValue;
int nBestIndex = -1;
for (int i = 0; i < length; i++)
if (inputs[i] > fBestValue) { nBestIndex = i; fBestValue = inputs[i]; }
// record this highest value
res[c] = nBestIndex;
// now blank out that index.
if(nBestIndex!=-1) inputs[nBestIndex] = minValue;
}
return res;
}
int hzToPeriodInSamples(double hz, int sampleRate)
{
return (int)(1 / (hz / (double)sampleRate));
}
double periodInSamplesToHz(int period, int sampleRate)
{
return 1 / (period / (double)sampleRate);
}
Thanks,
Niall.