jimkokko5
jimkokko5

Reputation: 88

Java .wav file frequency analysis - incorrect frequency

I'm working on a project where I have to analyze .wav files, which basically for me comes down to being able to display the frequencies that are present in said file. I'm using the WavFile class to read the file, and then I FFT them using the JTransforms class (I actually do a realForward since I'm feeding it real numbers only).

Everything seems to work fine, until I feed the data into Excel: I throw a line numeration in the first column (1-8000 for the file that I'm testing with) and I throw the output of the FFT into the next column. The file I'm feeding is a simple one-frequency sound, at 440Hz, with a duration of 1 second.

After seeing the graph, though, there's a problem: I have a single frequency peak, which is exactly what I'm expecting, but the peak is situated at position 880, which is double the actual frequency. Can somebody explain to me why is that?

Bonus question: Why am I getting values around e-16? Everything other than the peak should be 0, right? (I fix this by writing a 0 to the file each time the data I get is <= 1- see code below). Maybe it's "noise"?


Code:

There's two classes. The first one, readWav is used to, well, read the .wav file. The second one, wavFFT, is the one who actually FFTs the data.

Code for readWav.java:

import WavFile.*;
import java.io.*;

public class readWav {

    // getter methods
    public static long getWavFrames(File file)
    {
        // try loop to catch any exception
        try {
            // open the wav file
            WavFile wavFile = WavFile.openWavFile(file);

            // return the number of frames
            return wavFile.getNumFrames();

        } catch (Exception e) {
            System.err.println(e);

            // error value
            return -1;
        }

    }

    public static int getWavChannels(File file)
    {
        // try loop to catch any exception
        try {
            WavFile wavFile = WavFile.openWavFile(file);

             return wavFile.getNumChannels();

        } catch (Exception e) {
            System.err.println(e);

            // error value
            return -1;
        }
    }

    public static double[] getWavData(File file)
    {
        // try loop to catch any exception
        try {
            // open the file
            WavFile wavFile = WavFile.openWavFile(file);

            // use the getter method to get the channel number (should be mono)
            int numChannels = getWavChannels(file);

            // same, but with the frame getter method
            int numFrames = (int) getWavFrames(file); // possible data loss

            // create a buffer the size of the number of frames
            double[] buffer = new double[numFrames * numChannels];

            // Read frames into buffer
            wavFile.readFrames(buffer, numFrames);

            // Close the wavFile
            wavFile.close();

            return buffer;

        } catch (Exception e) {
            System.err.println(e);

            // throw an error, if this runs something went wrong in reading the .wav file
            throw new RuntimeException("[could not read wav file " + file + "]");
        }
    }


    // main method, solely for testing purposes
    public static void main(String[] args)
    {
        // test, everything seems to be working
        File fichier_son = new File("son/freq1.wav");
        double[] test = getWavData(fichier_son);
        for(int i = 0; i<test.length; i++){
            System.out.println(test[i]);
        }
    }

}

Code for wavFFT.java:

import org.jtransforms.fft.DoubleFFT_1D;
import java.io.File;
import java.io.PrintWriter;
import java.io.IOException;

public class wavFFT {

    public static double[] realFFT(File file)
    {
        // Get the .wav data using the readWav class
        double[] data_to_fft = readWav.getWavData(file);

        /* Get the length of the array.
        Since we are feeding real numbers into the fft,
        the length of the array should be equal to the
        number of frames, which we get using the readWav class. */
        int n = (int) readWav.getWavFrames(file);

        // Make a new fft object
        DoubleFFT_1D fft = new DoubleFFT_1D(n);

        // Perform the realForward fft
        fft.realForward(data_to_fft);

        // Return the final data
        return data_to_fft;
    }


    public static void writeToFile(File in, File out) throws IOException
    {
        PrintWriter print_out = new PrintWriter(out);
        int i;
        double[] data_to_file = realFFT(in);

        for(i=0; i<data_to_file.length; i++){
            if(data_to_file[i] > 1){
                print_out.println(data_to_file[i]);
            } else {
                print_out.println(0);
            }

        }
        print_out.close();
    }

    // main method, solely for testing purposes
    public static void main(String[] args) {
        File fichier_son = new File("son/freq1.wav");
        double[] test = realFFT(fichier_son); 
        int i;

        for(i=0; i<test.length; i++){
            System.out.println(test[i]);
        }


        try{
            writeToFile(fichier_son, new File("datafiles/output.txt"));
        } catch (IOException e){
            System.out.println("error");
        }
    }

}

Upvotes: 0

Views: 1959

Answers (1)

marko
marko

Reputation: 9159

You don't say how you interpret the results in Excel that you generate with the code above. However, a likely error is misunderstanding the output of the FFTfft.realForward() - which is an array of complex numbers, whose real and imaginary parts occupy consecutive elements, as documented here. If you simply use the index of the array in which the peak occurs, your result would be off by a factor of two. Note that this FFT implementation only calculates up to the Nyqvist rate (beyond this merely yields an 'alias').

Other things to note:

  • You are applying the rectangular window function to the samples. I'm frankly surprised the bleed in other bins is as small as 10e-16. There are better choices the your application.
  • The window appears to be the full length of the file, probably resulting in a huge FFT. As a general rule, power-of-2 FFTS are far more efficient. You would typically see frequency analysis performed on fixed length windows.
  • The FFT is in effect a series of band-pass filters, which are the weighted sum of input samples. A contribution to the non-zero values you see is simply floating point round-off errors. The fact that you're putting the whole file in the window, means that there are a great many operations that contribute to this.

Upvotes: 2

Related Questions