I wrote a simple application to prepare my self for some upcoming exams: a DFT implementation in Java that reads content from a 16bit mono PCM encoded Wave file. If you are interested, you can add some cool GUI and sonogram rendering to my code base. Otherwise feel free to use it in any way. It surely isn’t the best performing code, but it will do its work for basic research.
You can use the DFT class to generate output of the frequency spectrum on the terminal/command line. Copy it to Excel, Numbers, OpenOffice to make a visualization of the spectrum. Here are some samples:
-
-
a spectrum with 64 fourier channels
-
-
a spectrum with 128 fourier channels
-
-
a spectrum with 256 fourier channels
Main.java
public class Main {
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("please pass a 16bit PCM encoded Wave file as the first parameter");
System.exit(-1);
}
System.out.println("reading data from file: " + args[0]);
WaveResource waveResource = new WaveResource();
waveResource.loadFromFile(args[0]);
DFT dft = new DFT();
dft.setSamplesResource(waveResource);
dft.setWindowLength(64);
dft.doIt();
}
}
DFT.java
public class DFT {
private WaveResource waveResource;
private float[] samples;
private Complex[] coefficients;
private int windowLength;
private int numberOfFourierChannels;
private float precision;
private float fourierChannelWidth;
public DFT() {
System.out.println("Discrete Fourier Transformation created");
}
public void setSamplesResource(WaveResource wr) {
if (wr != null) {
waveResource = wr;
samples = waveResource.getSamplesAsFloat();
}
}
public void setWindowLength(int v) {
if (v > 0) {
windowLength = v;
precision = waveResource.getSampleRate() / (float) windowLength;
numberOfFourierChannels = (int) (waveResource.getSampleRate() / precision);
fourierChannelWidth = waveResource.getSampleRate() / (float) numberOfFourierChannels;
if (windowLength > samples.length) {
System.out.println("not enough samples to measure so many channels");
}
}
}
public void doIt() {
System.out.println("sampling rate: " + waveResource.getSampleRate());
System.out.println("precision: " + precision);
System.out.println("number of fourier channels: " + numberOfFourierChannels);
System.out.println("fourier channel width: " + fourierChannelWidth);
coefficients = new Complex[numberOfFourierChannels/2];
for (int omega = 0; omega < numberOfFourierChannels/2; omega++) {
Complex c = new Complex();
for (int i = 0; i < windowLength; i++) {
double phi = -2*Math.PI*omega*i/windowLength;
Complex e = new Complex(Math.cos(phi), Math.sin(phi));
c = c.add(e.mult(samples[i]));
}
coefficients[omega] = c.div(Math.sqrt(windowLength));
}
}
public void printCoefficients() {
for (int omega = 0; omega < numberOfFourierChannels/2; omega++) {
System.out.println(coefficients[omega].length());
}
}
}
WaveResource.java
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class WaveResource {
private String fileName;
File file;
FileInputStream fileInputStream;
private int[] samples;
private long chunkSize;
private String format;
private String subChunkID;
private long subChunkSize;
private int numberOfSamples;
private int numberOfChannels;
private long sampleRate;
private long byteRate;
private int bitsPerSample;
private int blockAlign;
private String subChunkID2;
private long subChunkSize2;
private int audioFormat;
private int maxWord;
private int minWord;
public WaveResource() {
System.out.println("WaveResource created");
}
public long getSampleRate() {
return sampleRate;
}
private void setBitsPerSample (int v) {
bitsPerSample = v;
maxWord = 0x00000000;
minWord = 0xFFFFFFFF;
for (int i = 0; i < v-1; i++) {
maxWord = maxWord << 1;
maxWord = maxWord | 0x1;
minWord = minWord << 1;
}
}
private long bytesToUnsignedInt (byte[] b) {
return ((long)
(0xFF & b[3]) << 24 |
(0xFF & b[2]) << 16 |
(0xFF & b[1]) << 8 |
(0xFF & b[0])) & 0xFFFFFFFFL;
}
private int bytesToUnsignedShort (byte[] b) {
return ((int)
(0xFF & b[1]) << 8 |
(0xFF & b[0])) & 0xFFFFFFFF;
}
private void load() {
try {
fileInputStream = new FileInputStream(file);
// Skipping "RIFF"
fileInputStream.skip(4);
byte[] chunkSizeBuffer = new byte[4];
fileInputStream.read(chunkSizeBuffer, 0, 4);
chunkSize = bytesToUnsignedInt(chunkSizeBuffer);
chunkSizeBuffer = null;
byte[] formatBuffer = new byte[4];
fileInputStream.read(formatBuffer, 0, 4);
format = new String(formatBuffer);
formatBuffer = null;
byte[] subChunkIDBuffer = new byte[4];
fileInputStream.read(subChunkIDBuffer, 0, 4);
subChunkID = new String(subChunkIDBuffer);
subChunkIDBuffer = null;
byte[] subChunkSizeBuffer = new byte[4];
fileInputStream.read(subChunkSizeBuffer, 0, 4);
subChunkSize = bytesToUnsignedInt(subChunkSizeBuffer);
subChunkSizeBuffer = null;
byte[] audioFormatBuffer = new byte[2];
fileInputStream.read(audioFormatBuffer, 0, 2);
audioFormat = bytesToUnsignedShort(audioFormatBuffer);
audioFormatBuffer = null;
byte[] numberOfChannelsBuffer = new byte[2];
fileInputStream.read(numberOfChannelsBuffer, 0, 2);
numberOfChannels = bytesToUnsignedShort(numberOfChannelsBuffer);
numberOfChannelsBuffer = null;
byte[] sampleRateBuffer = new byte[4];
fileInputStream.read(sampleRateBuffer, 0, 4);
sampleRate = bytesToUnsignedInt(sampleRateBuffer);
sampleRateBuffer = null;
byte[] byteRateBuffer = new byte[4];
fileInputStream.read(byteRateBuffer, 0, 4);
byteRate = bytesToUnsignedInt(byteRateBuffer);
byteRateBuffer = null;
byte[] blockAlignBuffer = new byte[2];
fileInputStream.read(blockAlignBuffer, 0, 2);
blockAlign = bytesToUnsignedShort(blockAlignBuffer);
blockAlignBuffer = null;
byte[] bitsPerSampleBuffer = new byte[2];
fileInputStream.read(bitsPerSampleBuffer, 0, 2);
setBitsPerSample(bytesToUnsignedShort(bitsPerSampleBuffer));
bitsPerSampleBuffer = null;
byte[] subChunkID2Buffer = new byte[4];
fileInputStream.read(subChunkID2Buffer, 0, 4);
subChunkID2 = new String(subChunkID2Buffer);
subChunkID2Buffer = null;
byte[] subChunkSize2Buffer = new byte[4];
fileInputStream.read(subChunkSize2Buffer, 0, 4);
subChunkSize2 = bytesToUnsignedInt(subChunkSize2Buffer);
subChunkSize2Buffer = null;
numberOfSamples = (int) 8 * (int)subChunkSize2 / (bitsPerSample * numberOfChannels);
byte[] dataBuffer = new byte[(int) subChunkSize2];
fileInputStream.read(dataBuffer, 0, (int) subChunkSize2);
if(bitsPerSample != 16) {
System.out.println("WaveLoader class currently only supports 16bit word length");
} else {
samples = new int[numberOfSamples];
for (int i = 0; i < dataBuffer.length; i+=2) {
int index = i/2;
samples[index] = (0xFF & dataBuffer[i+1]) << 8 | (0xFF & dataBuffer[i]); if (samples[index] > maxWord) {
samples[index] = samples[index] + 2 * minWord;
}
}
}
dataBuffer = null;
fileInputStream.close();
// printing some format information
System.out.println("chunk size: " + chunkSize);
System.out.println("audio format: " + format);
System.out.println("subchunk id: " + subChunkID);
System.out.println("subchunk size: " + subChunkSize);
System.out.println("audio format: " + audioFormat);
System.out.println("channels: " + numberOfChannels);
System.out.println("sample rate: " + sampleRate);
System.out.println("byte rate: " + byteRate);
System.out.println("block align: " + blockAlign);
System.out.println("bits per sample: " + bitsPerSample);
System.out.println("subchunk id 2: " + subChunkID2);
System.out.println("subchunk size 2: " + subChunkSize2);
System.out.println("Wave file successfully loaded! YEAH!");
} catch (FileNotFoundException e) {
System.out.println("the file " + fileName + " does not exist in the file system");
fileName = null;
file = null;
e.printStackTrace();
} catch (IOException e) {
System.out.println("the file " + fileName + " could not be read");
e.printStackTrace();
}
}
public void loadFromFile(String name) {
if(name != "" && name != null) {
fileName = name;
file = new File(fileName);
this.load();
}
}
public float[] getSamplesAsFloat() {
float[] floatSamples = new float[numberOfSamples];
for (int i = 0; i < numberOfSamples; i++) { if (samples[i] >= 0) {
floatSamples[i] = samples[i] / (float)maxWord;
} else {
floatSamples[i] = -samples[i] / (float)minWord;
}
}
return floatSamples;
}
}
Complex.java
public class Complex {
private final double r;
private final double i;
public Complex(double a, double b){
r = a;
i = b;
}
public Complex(){
r = 0;
i = 0;
}
public String toString(){
return r+"(r) "+i+"(i)";
}
public Complex mult(double a){
return new Complex(r*a, i*a);
}
public Complex mult(Complex c){
return new Complex(r*c.r - i*c.i, i*c.r + r*c.i);
}
public Complex div(double a){
return new Complex(r*(1.0d/a), i*(1.0d/a));
}
public Complex conjugate(){
return new Complex(r, -i);
}
public Complex add(Complex c) {
return new Complex(r+c.r, i+c.i);
}
public double length() {
return Math.sqrt(i*i+r*r);
}
public Complex normalize() {
return new Complex(r/length(), i/length());
}
}
Have fun