Dismiss Notice
Join Physics Forums Today!
The friendliest, high quality science and math community on the planet! Everyone who loves science is here!

Basic FFT to identify frequency

  1. Aug 26, 2013 #1
    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 (Text):

    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();
                im = 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();
        }
    }

     
     
  2. jcsd
  3. Aug 26, 2013 #2

    jtbell

    User Avatar

    Staff: Mentor

    Can you give a specific example that doesn't seem to produce good results?
     
  4. Aug 26, 2013 #3
    I am producing a sound freq. with my phone and freqs don't match.
     
  5. Aug 26, 2013 #4
    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: Aug 26, 2013
  6. Aug 26, 2013 #5
    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
     
  7. Aug 26, 2013 #6
    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.
     
Know someone interested in this topic? Share this thread via Reddit, Google+, Twitter, or Facebook




Similar Discussions: Basic FFT to identify frequency
  1. FFT library (Replies: 2)

  2. Fortran FFT library (Replies: 2)

  3. FFT code help please (Replies: 16)

  4. [C#] FFT in read world (Replies: 33)

Loading...