I am currently working on an OpenGL tuner app. I currently have the FFT plot on screen, and I would like to calculate the loudest frequency. Here is what my code looks like:
MainActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
transformer = new RealDoubleFFT(samples);
started = true;
recordAudio = new RecordAudio();
recordAudio.execute();
}
// ...
public class RecordAudio extends AsyncTask<Void, double[], Void> {
@Override
protected Void doInBackground(Void... arg0) {
try {
int bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate,
channelConfig, audioFormat, bufferSize);
short[] buffer = new short[samples];
double[] fft = new double[samples];
audioRecord.startRecording();
while (started) {
int bufferReadResult = audioRecord.read(buffer, 0, samples);
for (int i = 0; i < samples && i < bufferReadResult; i++) {
fft[i] = (double) buffer[i] / 32768.0;
}
transformer.ft(fft);
publishProgress(fft);
}
audioRecord.stop();
} catch (Throwable t) {
t.printStackTrace();
Log.e("MainActivity.AudioRecord", "The audio recording has encountered an error.");
}
return null;
}
@Override
protected void onProgressUpdate(double[]... fft) {
int peak = (int) ((frequency / ((float) sampleRate / (float) samples)) * 2f);
int offset = 0;
for (int i = 0; i < samples; i++) {
fftVertices[offset++] = (((float) i / (float) samples) * 2f) - 1f;
fftVertices[offset++] = -1f;
float color = 0;
if (i < peak) {
color = (float) (peak - i) / (float) peak;
} else if (i > peak) {
color = (float) (i - peak) / (float) ((int) ((float) samples / 2f) - peak);
} else {
color = 0f;
}
color = 1f - color;
fftVertices[offset++] = color;
fftVertices[offset++] = color;
fftVertices[offset++] = color;
fftVertices[offset++] = (((float) i / (float) samples) * 2f) - 1f;
fftVertices[offset++] = ((float) fft[0][i] / 2f) - 1f;
fftVertices[offset++] = color;
fftVertices[offset++] = color;
fftVertices[offset++] = color;
if (i % 2 == 0) {
magnitude[(int) ((float) i / 2f)] = (float) Math.sqrt(fft[0][i] * fft[0][i] + fft[0][i + 1] * fft[0][i + 1]);
}
}
updateFrequency(magnitude);
}
}
private static void updateFrequency(float[] mag) {
int peak = 0;
for (int i = 0; i < (int) ((float) samples / 2f); i++) {
if (mag[i] >= mag[peak]) {
peak = i;
}
}
frequency = peak * sampleRate / samples;
Log.d("MainActivity", "Frequency: " + frequency + " Hz");
}
private static float[] getVertices() {
return fftVertices;
}
private static class Renderer implements GLSurfaceView.Renderer {
// ...
private float[] vertices = new float[samples * 2 * (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT)];
public Renderer(Context context) {
// ...
int offset = 0;
for (int i = 0; i < samples; i++) {
vertices[offset++] = (((float) i / (float) samples) * 2f) - 1f;
vertices[offset++] = -1f;
vertices[offset++] = 1f;
vertices[offset++] = 1f;
vertices[offset++] = 1f;
vertices[offset++] = (((float) i / (float) samples) * 2f) - 1f;
vertices[offset++] = ((float) Math.random() * 2f) - 1f;
vertices[offset++] = 1f;
vertices[offset++] = 1f;
vertices[offset++] = 1f;
}
// ...
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// ...
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
glClear(GL_COLOR_BUFFER_BIT);
// ...
}
}
So the MainActivity
contains the RecordAudio
class. I left out some lines that weren't important, like OpenGL or application stuff. Basically, my application reads audio input and displays the FFT onscreen. I have set up a frequency calculation as follows:
I first make a float array of the magnitudes:
magnitude[(int) ((float) i / 2f)] = (float) Math.sqrt(fft[0][i] * fft[0][i] + fft[0][i + 1] * fft[0][i + 1]);
Here, inside a for loop which iterates through the FFT, each iteration where the index is even, a magnitude is added to the array. Then at the end, the frequency is calculated:
private static void updateFrequency(float[] mag) {
int peak = 0;
for (int i = 0; i < (int) ((float) samples / 2f); i++) {
if (mag[i] >= mag[peak]) {
peak = i;
}
}
frequency = peak * (int) ((float) sampleRate / (float) samples);
Log.d("MainActivity", "Frequency: " + frequency + " Hz");
}
By finding the peak position, then calculating the frequency.
My problem is: I am playing a 440 Hz sine wave into the phone's mic, and it's output is lots of sporadic numbers, but some of them are 430 Hz. Here is a sample output:
12-31 16:34:24.992 387.59766 Hz
12-31 16:34:25.022 430.66406 Hz
12-31 16:34:25.042 387.59766 Hz
12-31 16:34:25.072 430.66406 Hz
12-31 16:34:25.122 387.59766 Hz
12-31 16:34:25.142 430.66406 Hz
12-31 16:34:25.162 387.59766 Hz
12-31 16:34:25.182 430.66406 Hz
12-31 16:34:25.182 387.59766 Hz
12-31 16:34:25.192 430.66406 Hz
12-31 16:34:25.222 430.66406 Hz
12-31 16:34:25.242 387.59766 Hz
12-31 16:34:25.262 430.66406 Hz
12-31 16:34:25.292 430.66406 Hz
12-31 16:34:25.312 387.59766 Hz
12-31 16:34:25.332 387.59766 Hz
12-31 16:34:25.372 430.66406 Hz
12-31 16:34:25.392 387.59766 Hz
12-31 16:34:25.422 430.66406 Hz
12-31 16:34:25.432 1722.6563 Hz
12-31 16:34:25.452 387.59766 Hz
12-31 16:34:25.472 430.66406 Hz
12-31 16:34:25.502 387.59766 Hz
12-31 16:34:25.522 430.66406 Hz
12-31 16:34:25.553 387.59766 Hz
12-31 16:34:25.573 387.59766 Hz
12-31 16:34:25.603 430.66406 Hz
12-31 16:34:25.623 387.59766 Hz
How can I achieve a more stable and accurate result?
One of the issues of a DFT is that if your peak is wide and lies over two (or more) bins and it shifts ever so slightly (because of doppler or other reasons), you'll get fluctuations of energy between the two bins.
One way is to increase the number of bins of the FFT (number of points of your FFT). You may want to consider Zero-padding for that as well.
If you don't want to mess around with number of points of FFT, another approach is to interpolate peak value (and frequency) from the FFT data. This article gives good approach to such a technique.
I am not sure about accuracy, but this should help with stability (reduce the fluctuation) of the result in my opinion.