/*
* SlidingDFT.java
* Eisenkraut
*
* Copyright (c) 2004-2016 Hanns Holger Rutz. All rights reserved.
*
* This software is published under the GNU General Public License v3+
*
*
* For further information, please contact Hanns Holger Rutz at
* contact@sciss.de
*/
package de.sciss.eisenkraut.math;
/**
* For the sliding DFT algorithm, see for example
* Bradford/Dobson/ffitch "Sliding is smoother than jumping".
*/
public class SlidingDFT
{
private final int fftSize;
private final int fftSizeP2;
private final int bins;
// private final int binsQ1;
// private final int binsQ3;
private final double[] cos;
private final double[] sin;
private final float[][] timeBuf;
private final double[][] fftBufD;
private int[] timeBufIdx;
public SlidingDFT( int fftSize, int numChannels )
{
this.fftSize = fftSize;
final double d1;
final int binsH;
double d2;
bins = fftSize >> 1;
fftSizeP2 = fftSize + 2;
fftBufD = new double[ numChannels ][ fftSizeP2 ];
d1 = MathUtil.PI2 / fftSize;
// d1 = Math.PI / fftSize;
// binsQ1 = bins >> 2;
// fftSizeQ2 = fftSizeQ1 << 1;
binsH = bins >> 1;
// binsQ = bins >> 2;
// binsQ3 = bins - binsQ1;
// well we could save 25% of the storage tables
// by calculating the cosine table till 1.5pi instead
// of pi and using it for sine lookup
cos = new double[ bins + 1 ];
sin = new double[ bins + 1 ];
timeBuf = new float[ numChannels ][ fftSize ];
timeBufIdx = new int[ numChannels ];
// note that cos[ binsH ] == 0, so we can write i < binsH instead of i <= binsH
// and don't invert the roundoff error here.
//double phas = Math.PI/2;
for( int i = 0, j = bins, k = binsH, m = binsH; i < binsH; i++, j--, k--, m++ ) {
d2 = Math.cos( d1 * i );
cos[ i ] = d2;
cos[ j ] = -d2;
sin[ k ] = d2;
sin[ m ] = d2;
}
}
public void next( float[] inBuf, int inOff, int len, int chan, float[] fftBuf )
{
// formula: ("The Sliding DFT" Tutorial)
// Xk[n] = (Xk[n-1] - x[n-N] + x[n]) * exp( j2pi*k/N )
final double[] fftBufDC = fftBufD[ chan ];
final float[] timeBufC = timeBuf[ chan ];
int timeBufIdxC = timeBufIdx[ chan ];
double delta, re1, im1, re2, im2;
float f1;
for( int i = 0, j = inOff; i < len; i++, j++ ) {
f1 = inBuf[ j ];
delta = (double) f1 - (double) timeBufC[ timeBufIdxC ];
timeBufC[ timeBufIdxC ] = f1;
for( int k = 0, m = 0; m < fftSizeP2; k++ ) {
// Unfortunately we cannot just add the real input since due to
// fftBuf buffer rotation, new samples don't come in at index
// fftSize - 1, but instead add fftSize/2 - 1!
// re1 = fftBufDC[ m ] + delta;
// im1 = fftBufDC[ m + 1 ];
// this is the theoretical rotation needed for the fftBuf rotation
// double deltaRe = delta * Math.cos( MathUtil.PI2 * k * bins / fftSize );
// double deltaIm = delta * Math.sin( MathUtil.PI2 * k * bins / fftSize );
// obviously cos( PI * k ) cycles 1, -1, 1, -1 etc. ; and sin( PI * k ) is zero!
// double deltaRe = delta * Math.cos( Math.PI * k );
// double deltaIm = delta * Math.sin( Math.PI * k );
// re1 = fftBufDC[ m ] + deltaRe;
// im1 = fftBufDC[ m + 1 ] + deltaIm;
// so here's the easy one:
if( (k & 1) == 0 ) {
re1 = fftBufDC[ m ] + delta;
} else {
re1 = fftBufDC[ m ] - delta;
}
im1 = fftBufDC[ m + 1 ];
re2 = cos[ k ];
im2 = sin[ k ];
// // sin( x ) == cos( x + 1.5pi )
// im2 = cos[ (k + binsQ3) % bins ];
//re2 = Math.cos( -MathUtil.PI2 * k / fftSize );
//if( i == 0 ) System.out.println( "Should be " + re2 + ", but is " + cos[k ]);
//im2 = Math.sin( -MathUtil.PI2 * k / fftSize );
//if( i == 0 ) System.out.println( "Should be " + im2 + ", but is " + sin[k ]);
// fftBufDC[ m++ ] = re1 * re2 - im1 * im2;
// fftBufDC[ m++ ] = re1 * im2 + re2 * im1;
fftBufDC[ m++ ] = re1 * re2 - im1 * im2; // + deltaRe;
fftBufDC[ m++ ] = re1 * im2 + re2 * im1; // + deltaIm;
}
if( ++timeBufIdxC == fftSize ) timeBufIdxC = 0;
}
timeBufIdx[ chan ] = timeBufIdxC;
// now cast the internal fftBufDC back to fftBuf
for( int i = 0; i < fftSizeP2; i++ ) {
fftBuf[ i ] = (float) fftBufDC[ i ];
}
// this is to compare with non-sliding variant
// int gaga = timeBufIdxC;
// for( int i = 0; i < fftSize; i++ ) {
// fftBuf[ (i + (fftSize >> 1)) % fftSize ] = timeBufC[ gaga % fftSize ];
//// fftBuf[ i ] = timeBufC[ gaga % fftSize ];
// gaga++;
// }
// Fourier.realTransform( fftBuf, fftSize, Fourier.FORWARD );
}
}