package kj.dsp;
/**
* @author Kris
*
* From https://code.google.com/p/libkj-java/
*/
public class KJFFT {
private float[] xre;
private float[] xim;
private float[] mag;
private float[] fftSin;
private float[] fftCos;
private int[] fftBr;
private int ss, ss2, nu, nu1;
/**
* @param The amount of the sample provided to the "calculate" method to use during
* FFT calculations.
*/
public KJFFT( int pSampleSize ) {
ss = pSampleSize;
ss2 = ss >> 1;
xre = new float[ ss ];
xim = new float[ ss ];
mag = new float[ ss2 ];
nu = (int)( Math.log( ss ) / Math.log( 2 ) );
nu1 = nu - 1;
prepareFFTTables();
}
private int bitrev( int j, int nu ) {
int j1 = j;
int j2;
int k = 0;
for( int i = 1; i <= nu; i++ ) {
j2 = j1 >> 1;
k = ( k << 1 ) + j1 - ( j2 << 1 );
j1 = j2;
}
return k;
}
/**
* @param pSample The sample to compute FFT values on.
* @return The results of the calculation, normalized between 0.0 and 1.0.
*/
public float[] calculate( float[] pSample ) {
int n2 = ss2;
int nu1 = nu - 1;
int wAps = pSample.length / ss;
// -- FIXME: This affects the calculation accuracy, because
// is compresses the digital signal. Looks nice on
// the spectrum analyser, as it chops off most of
// sound we cannot hear anyway.
for ( int a = 0, b = 0; a < pSample.length; a += wAps, b++ ) {
xre[ b ] = pSample[ a ];
xim[ b ] = 0.0f;
}
float tr, ti, c, s;
int k, kn2, x = 0;
for ( int l = 1; l <= nu; l++ ) {
k = 0;
while ( k < ss ) {
for ( int i = 1; i <= n2; i++ ) {
// -- Tabled sin/cos
c = fftCos[ x ];
s = fftSin[ x ];
kn2 = k + n2;
tr = xre[ kn2 ] * c + xim[ kn2 ] * s;
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++;
}
k += n2;
}
nu1--;
n2 >>= 1;
}
int r;
// -- Reorder output.
for( k = 0; k < ss; k++ ) {
// -- Use tabled BR values.
r = fftBr[ k ];
if ( r > k ) {
tr = xre[ k ];
ti = xim[ k ];
xre[ k ] = xre[ r ];
xim[ k ] = xim[ r ];
xre[ r ] = tr;
xim[ r ] = ti;
}
}
// -- Calculate magnitude.
mag[ 0 ] = (float)( Math.sqrt( xre[ 0 ] * xre[ 0 ] + xim[ 0 ] * xim[ 0 ] ) ) / ss;
for ( int i = 1; i < ss2; i++ ) {
mag[ i ]= 2 * (float)( Math.sqrt( xre[ i ] * xre[ i ] + xim[ i ] * xim[ i ] ) ) / ss;
}
return mag;
}
private void prepareFFTTables() {
int n2 = ss2;
int nu1 = nu - 1;
// -- Allocate FFT SIN/COS tables.
fftSin = new float[ nu * n2 ];
fftCos = new float[ nu * n2 ];
float tr, ti, p, arg;
int k = 0, x = 0;
// -- Prepare SIN/COS tables.
for ( int l = 1; l <= nu; l++ ) {
while ( k < ss ) {
for ( int i = 1; i <= n2; i++ ) {
p = bitrev( k >> nu1, nu );
arg = 2 * (float)Math.PI * p / 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 );
}
}
}