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?