0

I am using FFT to calculate the frequency of a sustained note through a device mic. I am using JTransform to calculate the FFT. The following is the code:

//Mic reading variables
int audioSource = MediaRecorder.AudioSource.MIC;
// Audio source is the device mic
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
// Recording in mono
int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
// Records in 16bit
//Frequency calculation variables
double[] audioDataDoubles;
private DoubleFFT_1D fft;
// The fft double array I am unsure if these values are correct. I cant seem to find a good balance
int blockSize = 256;
// deal with this many samples at a time
int sampleRate = 8000;
// Sample rate in Hz
double[] ringBuffer = new double[10];
int ring = 0;
double avgFreq = 0.0;
double smoothing = 20.0;
// The power for the low pass filter, The higher the more powerful

My low pass filter

//Low pass Filter
public void smoothArray(double[] audio, double smoothing){
  /* The Low pass filter removes the high frequency changes to signal.
  * That being background noise, e.g. hum of computers*/
  // Takes the first audio data value
  double smooth = audio[0];
  Long lastUpdate = System.currentTimeMillis()/1000;
  for(int i = 1; i < audio.length; i++){
    Long now = System.currentTimeMillis()/1000;
    double currentValue = audio[i];
    /*Calculates the difference of two signals and 
    * divides it by the smoothing power.
    * A Smoothing power of 1 will leave the data untouched.
    * A higher number will remove the high frequency.
    */
    Long elapsedTime = now - lastUpdate;
    double elapsed = elapsedTime.doubleValue();
    smooth += elapsed * (currentValue - smooth) / smoothing;
    lastUpdate = now;
    audio[i] = smooth;
  }
}

The record class

short[] buffer = new short[blockSize];
// Save the raw PCM samples as short bytes
audioDataDoubles = new double[(blockSize*2)];
// Same values as above, as doubles
int bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioEncoding);    
// Gets the minimum buffer needed
audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig,
  audioEncoding, bufferSize);
//bufferSize
audioRecord.startRecording();
// Start working
record = true;
// mic in use
fft = new DoubleFFT_1D(blockSize);
while(started){
  /* Reads the data from the microphone. it takes in data
  * to the size of the window "blockSize". The data is then
  * given in to audioRecord. The int returned is the number
  * of bytes that were read*/
  int bufferReadResult = audioRecord.read(buffer, 0, blockSize);
  // Read in the data from the mic to the array
  // takes from buffer and passes to audiodataDoubles
  fillArray(audioDataDoubles, buffer, blockSize, bufferReadResult);
}
//Apply the low pass filter to remove noise
smoothArray(audioDataDoubles, smoothing);
//audiodataDoubles now holds data to work with
fft.complexForward(audioDataDoubles);
double[] re = new double[blockSize];
double[] im = new double[blockSize];
double[] magnitude = new double[blockSize];
// Calculate the Real and imaginary and Magnitude.
for(int i = 0; i < blockSize; i++){
  // real is stored in first part of array
  re[i] = audioDataDoubles[i*2];
  // imaginary is stored in the sequential part
  im[i] = audioDataDoubles[(i*2)+1];
  // magnitude is calculated by the square root of (imaginary^2 + real^2)
  magnitude[i] = Math.sqrt((re[i] * re[i]) + (im[i]*im[i]));
}
double peak = -1.0;
// Get the largest magnitude peak
for(int i = 0; i < blockSize; i++){
  if(peak < magnitude[i])
    peak = magnitude[i];
}
// calculated the frequency
frequency = sampleRate * peak/blockSize;
ringBuffer[ring] = frequency;
ring++;
if(ring == (ringBuffer.length -1)){
  for(int j = 0; j < ring; j++){
    avgFreq = avgFreq + ringBuffer[j];
  }
  double avg = (double) ring;
  avgFreq = avgFreq/avg;
  Log.i("AudioRecord", "HZ: " + avgFreq);
  /* calls onProgressUpdate
  * publishes the frequency
  */
  publishProgress(avgFreq);
  //restart the ring buffer
  ring = 0;
}

The frequencies returned are incorrect and not constant. I would be expecting at least a constant frequency number, however it changes constantly. For instance reading in 440.1 hz returns 290hz-390hz. I have run a graph and it provides an expected peak that does not change. Can anyone spot my mistake? Thank you.

toto2
  • 5,306
  • 21
  • 24
  • Is this question distinct from your previous question? (http://stackoverflow.com/questions/16762667/fft-guitar-tuner-application-incorrect-frequncy) – Oliver Charlesworth May 28 '13 at 12:45
  • I have implemented the options from previous questions. I have included the ring buffer and I had tried increasing the fft sample window. both to no avail. – Cillian Donlon May 28 '13 at 12:47
  • 1
    You don't seem to have addressed the problem of a lack of window function prior to the FFT? – Paul R May 28 '13 at 14:32
  • Could you recommend a window function or sample code/example of implementing one? – Cillian Donlon May 28 '13 at 14:54
  • 1
    There are quite a few good answers right here on SO on this very subject - also the [Wikipedia page on window functions](http://en.wikipedia.org/wiki/Window_function) is a good starting point. A simple Hann or Hamming window should be all you need. – Paul R May 28 '13 at 15:22
  • I have read up on the hann window, and have checked so of your answers to previous questions @PaulR So getting this straight. I apply this to buffer, then pass it to my audioDataDaouble array to do my fft work? Do I apply the window to the whole sample? – Cillian Donlon May 28 '13 at 16:00
  • 1
    You apply the window function to the block of samples that you are going to pass to the FFT. So if your FFT size is say 2048 then you will apply to a 2048 point window function to a block of 2048 samples and then pass those windowed samples to the FFT. – Paul R May 28 '13 at 16:03
  • The note given from an instrument produces very wrong frequencies, again not stable – Cillian Donlon May 28 '13 at 17:53
  • The `smoothArray` function is pretty wrong, assuming that System.currentTimeMillis will do anything useful. – Pointer Null Jan 07 '15 at 00:28

1 Answers1

1

It might not be an answer to your fundamental problem, but the low-pass filter is certainly wrong: you are using time from the clock (System.currentTimeMillis()), but the audio data has already been captured at some previous time intervals.

I'm not sure what the filter should do. But it says in your comment that smoothing=1 should leave the data unchanged and I don't see that this is the case.

toto2
  • 5,306
  • 21
  • 24
  • Thank you, The filter was incorrect. I have removed it and the frequency readouts are lot more stable. however, they are still incorrect. – Cillian Donlon May 28 '13 at 14:51