Why Are FFT Frequencies Inaccurate in My Java Program?

  • Context: Java 
  • Thread starter Thread starter joseche
  • Start date Start date
  • Tags Tags
    Fft Frequency
Click For Summary

Discussion Overview

The discussion revolves around the inaccuracies in frequency detection using a Java program that implements Fast Fourier Transform (FFT) to analyze audio input from a microphone. Participants explore potential causes for the discrepancies in frequency readings and suggest various approaches to improve accuracy, including the use of window functions and adjustments to the data processing method.

Discussion Character

  • Exploratory
  • Technical explanation
  • Debate/contested
  • Mathematical reasoning

Main Points Raised

  • One participant reports that the frequencies detected by their program do not match the expected values when producing sound frequencies with a phone.
  • Another participant suggests that inaccuracies may arise from missing a scaling factor or from the input frequency not being an exact multiple of the FFT sampling frequency, recommending the use of window functions to mitigate spectral leakage.
  • A participant shares that they identified the peak frequency in the second half of the FFT output and adjusted their data by dividing the casted double from 'short' by 32768, which improved the accuracy of note recognition.
  • There is a discussion about the necessity of using a window function, with suggestions to try Hann or Hamming windows and to test the results with various input signals.
  • Participants express uncertainty about the need for window functions depending on the characteristics of the input signals and the desired outcomes.

Areas of Agreement / Disagreement

Participants do not reach a consensus on the necessity of window functions, with some suggesting their use while others remain uncertain about their impact on the results. The discussion includes multiple competing views on how to address the frequency detection inaccuracies.

Contextual Notes

Limitations include the potential for spectral leakage due to the lack of windowing, the dependence on the sampling frequency, and the specific characteristics of the input signals used for testing.

joseche
Messages
3
Reaction score
0
Hi there, I am using a basic java program to identify the frequency of a note captured by the microphone.

The problem is that frequencies don't seem accurate.

Code:
package com.overdrive.FreqFinder;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;

import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.transform.DftNormalization;
import org.apache.commons.math3.transform.FastFourierTransformer;
import org.apache.commons.math3.transform.TransformType;

public class AudioInput {
	
	TargetDataLine 	microphone;
	
	final int 		audioFrames= 2048;  //power ^ 2
	
	final float 	sampleRate= 8000.0f;
	final int 		bitsPerRecord= 16;
	final int 		channels= 1;
	final boolean 	bigEndian = true;
	final boolean 	signed= true;
	
	byte 			byteData[]; 	// length=audioFrames * 2
	double 			doubleData[];	// length=audioFrames only reals needed for apache lib.
	AudioFormat 	format;
	FastFourierTransformer transformer;
	
	double frequencyTable[][]= new double[12][8]; //12 notes, 8 octaves
	
	String notes[];
	
	public AudioInput () {
		byteData= new byte[audioFrames * 2];  //two bytes per audio frame, 16 bits
		
		//doubleData= new double[audioFrames * 2];  // real & imaginary
		doubleData= new double[audioFrames];  // only real for apache
		
		transformer = new FastFourierTransformer(DftNormalization.STANDARD);
		
		System.out.print("Microphone initialization\n");
		format = new AudioFormat(sampleRate, bitsPerRecord, channels, signed, bigEndian);
		DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); // format is an AudioFormat object
		
		if (!AudioSystem.isLineSupported(info)) {
			System.err.print("isLineSupported failed");
			System.exit(1);
		}
		
		try {
			 microphone = (TargetDataLine) AudioSystem.getLine(info);
		     microphone.open(format);
		     System.out.print("Microphone opened with format: "+format.toString()+"\n");
		     microphone.start();
		}catch(Exception ex){
			System.out.println("Microphone failed: "+ex.getMessage());
			System.exit(1);
		}
		
		notes= new String[12];
		notes[0]= "C";
		notes[1]= "C#";
		notes[2]= "D";
		notes[3]= "D#";
		notes[4]= "E";
		notes[5]= "F";
		notes[6]= "F#";
		notes[7]= "G";
		notes[8]= "G#";
		notes[9]= "A";
		notes[10]= "A#";
		notes[11]= "B";
		buildFreqTable();
		
		
	}
	
	public int readPcm(){
		int numBytesRead= 
				microphone.read(byteData, 0, byteData.length);
		if(numBytesRead!=byteData.length){
			System.out.println("Warning: read less bytes than buffer size");
			System.exit(1);
		}
		return numBytesRead;
	}
	
	
	public void byteToDouble(){
		ByteBuffer buf= ByteBuffer.wrap(byteData);
		buf.order(ByteOrder.BIG_ENDIAN);
		int i=0; 
		
		while(buf.remaining()>2){
			short s = buf.getShort();
			doubleData[ i ] = (new Short(s)).doubleValue();
			++i;
		}
	}
	
	public void buildFreqTable(){
		
		frequencyTable[0][0]= 32.7032;  // C
		frequencyTable[1][0]= 34.6478;  // C#
		frequencyTable[2][0]= 36.7081;  // D
		frequencyTable[3][0]= 38.8909;  // D#
		frequencyTable[4][0]= 41.2034;  // E
		frequencyTable[5][0]= 43.6535;  // F
		frequencyTable[6][0]= 46.2493;  // F#
		frequencyTable[7][0]= 48.9994;  // G
		frequencyTable[8][0]= 51.9131;  // G#
		frequencyTable[9][0]= 55.0000;  // A
		frequencyTable[10][0]= 58.2705;  // A#
		frequencyTable[11][0]= 61.7354;  // B
		
		for (int note=0; note<12; note++){
			for(int oct=1; oct<8; oct++){
				frequencyTable[note][oct]= frequencyTable[note][oct-1]*2;
			}
		}
	}
	
	public int findNearFreq(double freq){
		double dMin= Double.MAX_VALUE, dc;
		int curNote=0;
		
		for (int note=0; note<12; note++){
			for (int oct=0; oct<8; oct++){
				
				//calculate the distance
				if( frequencyTable[note][oct] > freq )
					dc= frequencyTable[note][oct] - freq;
				else dc= freq - frequencyTable[note][oct];
				
				// see if new closer distance
				if (dMin > dc){
					dMin= dc;
					curNote= note;
				}
					
			}
		}
		return curNote;
	}
	
	public String noteName(int n){
		return notes[n];
	}
	
	
	public void findFrequency(){
		double frequency;
		
		Complex[] cmplx= transformer.transform(doubleData, TransformType.FORWARD);
		double real;
		double im;
		double mag[] = new double[cmplx.length];
		
		for(int i = 0; i < cmplx.length; i++){
            real = cmplx[i].getReal();
            I am = cmplx[i].getImaginary();
            mag[i] = Math.sqrt((real * real) + (im*im));
        }
		
		double peak = -1.0;
		int index=-1;
        for(int i = 0; i < cmplx.length; i++){
            if(peak < mag[i]){
                index=i;
                peak= mag[i];
            }
        }
        
        frequency = (sampleRate * index) / audioFrames;
        
        System.out.print("Index: "+index+", Frequency: "+frequency+", Note: "+ noteName(findNearFreq(frequency)) +"\n");
   }
	
	/*
	 * Print the first frequency bins to know about the resolution we have
	 */
	public void printFreqs(){
		for (int i=0; i<audioFrames/4; i++){
			System.out.println("bin "+i+", freq: "+(sampleRate*i)/audioFrames);
		}
	}
	
	public static void main(String[] args) {
		AudioInput ai= new AudioInput();
		int turns=10000;
		while(turns-- > 0){
			ai.readPcm();
			ai.byteToDouble();
			ai.findFrequency();
		}
		//ai.printFreqs();
	}
}
 
Technology news on Phys.org
Can you give a specific example that doesn't seem to produce good results?
 
I am producing a sound freq. with my phone and freqs don't match.
 
Two common possibilities.

1. missing a scaling factor, often of a rational times Pi.

2. input frequency is not an exact multiple of your fft sampling frequency and you are not windowing your data. Check Window function - Wikipedia, the free encyclopedia and Spectral leakage - Wikipedia, the free encyclopedia Try a nice clean single frequency for input, plot the the entire output from the fft and see if you get a nice clean single peak or if you get a broad range of results. If the latter then see if a good windowing function or sampling at an exact multiple of your possible input frequencies will help.
 
Last edited:
Hi Bill,

I did a few things, first I plotted the output of FFT and found a few things:
1) the peak was always in the second part of the output, which is the mirrored value?, so I only scan for the peak mag in the first halt
2) I divided the casted double from 'short' by 32768, because double works better in that range

Those two things seem to have fixed my code as it is recognizing notes very accurately.

I didn't use a window function although it seems I should ?, if that is the case, which one should I use ?

Thanks
 
joseche said:
I didn't use a window function although it seems I should ?, if that is the case, which one should I use ?

I'm glad you were able to able to get your code working.

There are many more windowing functions now than when I used to do this.

I'd try Hann or Hamming, give your code input signals that are nice clean simple multiples of your sampling frequency and in phase with the sampling, and compare the result with and without the window, see the maximum and plot the whole output. Then I'd progressively try less nice input signals, shift away from the sampling frequency, shift away the phase and repeat the process.

Depending on what your real input signals are going to be, you may or may not need a window if all you are trying to do is get the peak.
 

Similar threads

  • · Replies 1 ·
Replies
1
Views
1K
  • · Replies 8 ·
Replies
8
Views
2K
  • · Replies 7 ·
Replies
7
Views
2K
  • · Replies 1 ·
Replies
1
Views
2K
Replies
1
Views
2K
  • · Replies 1 ·
Replies
1
Views
3K
Replies
4
Views
3K
  • · Replies 1 ·
Replies
1
Views
2K
  • · Replies 2 ·
Replies
2
Views
2K
  • · Replies 3 ·
Replies
3
Views
3K