/**
* Xtreme Media Player a cross-platform media player.
* Copyright (C) 2005-2011 Besmir Beqiri
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package xtrememp.visualization.spectrum;
import java.nio.FloatBuffer;
/**
* Fast Fourier Transformation class used for calculating the realtime spectrum
* analyzer.
*
* Based on KJ-DSS project by Kristofer Fudalewski (http://sirk.sytes.net).
*
* @author Besmir Beqiri
*/
public class FFT {
private float[] xre;
private float[] xim;
private float[] mag;
private float[] fftSin;
private float[] fftCos;
private int[] fftBr;
private int ss, ss2, nu;
/**
* @param sampleSize The amount of the sample provided to the "calculate"
* method to use during FFT calculations, this is used to prepare the
* calculation tables in advance. This value is automatically rounded up to
* the nearest power of 2.
*/
public FFT(int sampleSize) {
nu = (int) Math.ceil(Math.log(sampleSize) / Math.log(2.0D));
// -- Calculate the nearest sample size to a power of 2.
ss = (int) Math.pow(2.0D, nu);
ss2 = ss >> 1;
// -- Allocate calculation buffers.
xre = new float[ss];
xim = new float[ss];
mag = new float[ss2];
// -- Allocate FFT SIN/COS tables.
fftSin = new float[nu * ss2];
fftCos = new float[nu * ss2];
prepareTables();
}
/**
* Bit swapping method.
*/
private int bitrev(int j, int nu) {
int j1 = j;
int k = 0;
for (int i = 0; i < nu; i++) {
int j2 = j1 >> 1;
k = (k << 1) + j1 - (j2 << 1);
j1 = j2;
}
return k;
}
/**
* Converts sound data over time into pressure values. (FFT)
*
* @param sample the sample to compute FFT values on.
* @return the results of the calculation, normalized between 0.0 and 1.0.
*/
public float[] calculate(FloatBuffer sample) {
int n2 = ss2;
// -- Fill buffer.
for (int a = 0, len = sample.capacity(); a < len; a++) {
xre[a] = sample.get(a);
xim[a] = 0.0f;
}
// -- Clear the remainder of the buffer.
for (int a = sample.capacity(); a < ss; a++) {
xre[a] = 0.0f;
xim[a] = 0.0f;
}
int x = 0;
for (int l = 0; l < nu; l++) {
for (int k = 0; k < ss; k += n2) {
for (int i = 0; i < n2; i++) {
// -- Tabled sin/cos
final float c = fftCos[x];
final float s = fftSin[x];
final int kn2 = k + n2;
final float tr = xre[kn2] * c + xim[kn2] * s;
final float ti = xim[kn2] * c - xre[kn2] * s;
xre[kn2] = xre[k] - tr;
xim[kn2] = xim[k] - ti;
xre[k] += tr;
xim[k] += ti;
k++;
x++;
}
}
n2 >>= 1;
}
// -- Reorder output.
for (int k = 0; k < ss; k++) {
// -- Use tabled BR values.
final int r = fftBr[k];
if (r > k) {
final float tr = xre[k];
xre[k] = xre[r];
xre[r] = tr;
final float ti = xim[k];
xim[k] = xim[r];
xim[r] = ti;
}
}
// -- Calculate magnitude.
for (int i = 0; i < ss2; i++) {
mag[i] = Math.abs(((float) (Math.sqrt((xre[i] * xre[i]) + (xim[i] * xim[i]))) / ss));
}
return mag;
}
/**
* Calculates a table of frequencies represented by the amplitude data
* returned by the 'calculate' method. Each element states the end of the
* frequency range of the corresponding FFT band (or bin). For example:
*
* Range of band 0 = 0.0 hz to frequencyTable[ 0 ] hz
* Range of band 1 = frequencyTable[ 0 ] hz to frequencyTable[ 1 ] hz
* Range of band 2 = frequencyTable[ 1 ] hz to frequencyTable[ 2 ] hz
* ... and so on.
*
* Calculation uses the sample size rounded to the nearest power of 2 of
* the FFT instance and the sample rate parameter to build this table.
*
* @param sampleRate The sample rate used to calculate the frequency table.
* Usually the sample rate of the input to the FFT
* calculate method.
* @return An array of frequency limits for each band.
*/
public float[] calculateFrequencyTable(float sampleRate) {
float fr = sampleRate / 2.0f;
//Calculate band width.
float bw = fr / ss2;
//Store for frequency table.
float[] ft = new float[(int) ss2];
//Build band range table.
int b = 0;
for (float fp = (bw / 2.0f); fp <= fr; fp += bw) {
ft[b] = fp;
b++;
}
return ft;
}
/**
* Returns the sample size this FFT instance uses for processing.
* It is automatically rounded to the nearest power of 2.
*
* @return The sample size used by the calculate method.
*/
public int getInputSampleSize() {
return ss;
}
/**
* Returns the sample size this FFT instance returns after processing.
* It is automatically rounded to the nearest power of 2.
*
* @return The sample size returned by the calculate method.
*/
public int getOutputSampleSize() {
return ss2;
}
/**
* Pre-calculates SIN/COS and bitrev tables in memory.
*/
private void prepareTables() {
int n2 = ss2;
int nu1 = nu - 1;
int k = 0;
int x = 0;
// -- Prepare SIN/COS tables.
for (int l = 0; l < nu; l++) {
while (k < ss) {
for (int i = 0; i < n2; i++) {
double p = bitrev(k >> nu1, nu);
double arg = (Math.PI * p * 2.0D) / (double) ss;
fftSin[x] = (float) Math.sin(arg);
fftCos[x] = (float) Math.cos(arg);
k++;
x++;
}
k += n2;
}
k = 0;
nu1--;
n2 >>= 1;
}
// -- Prepare bitrev table.
fftBr = new int[ss];
for (k = 0; k < ss; k++) {
fftBr[k] = bitrev(k, nu);
}
}
}