summaryrefslogtreecommitdiff
path: root/src/loris/FourierTransform.C
diff options
context:
space:
mode:
authorJohn Glover <glover.john@gmail.com>2011-07-08 18:06:21 +0100
committerJohn Glover <glover.john@gmail.com>2011-07-08 18:06:21 +0100
commitd6073e01c933c77f1e2bc3c3fe1126d617003549 (patch)
tree695d23677c5b84bf3a0f88fbd4959b4f7cbc0e90 /src/loris/FourierTransform.C
parent641688b252da468eb374674a0dbaae1bbac70b2b (diff)
downloadsimpl-d6073e01c933c77f1e2bc3c3fe1126d617003549.tar.gz
simpl-d6073e01c933c77f1e2bc3c3fe1126d617003549.tar.bz2
simpl-d6073e01c933c77f1e2bc3c3fe1126d617003549.zip
Start adding Loris files
Diffstat (limited to 'src/loris/FourierTransform.C')
-rw-r--r--src/loris/FourierTransform.C641
1 files changed, 641 insertions, 0 deletions
diff --git a/src/loris/FourierTransform.C b/src/loris/FourierTransform.C
new file mode 100644
index 0000000..2a29238
--- /dev/null
+++ b/src/loris/FourierTransform.C
@@ -0,0 +1,641 @@
+/*
+ * This is the Loris C++ Class Library, implementing analysis,
+ * manipulation, and synthesis of digitized sounds using the Reassigned
+ * Bandwidth-Enhanced Additive Sound Model.
+ *
+ * Loris is Copyright (c) 1999-2010 by Kelly Fitz and Lippold Haken
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *
+ * FourierTransform.C
+ *
+ * Implementation of class Loris::FourierTransform, providing a simplified
+ * uniform interface to the FFTW library (www.fftw.org), version 2.1.3
+ * or newer (including version 3), or to the General Purpose FFT package
+ * by Takuya OOURA, http://momonga.t.u-tokyo.ac.jp/~ooura/fft.html if
+ * FFTW is unavailable.
+ *
+ * Kelly Fitz, 2 Jun 2006
+ * loris@cerlsoundgroup.org
+ *
+ * http://www.cerlsoundgroup.org/Loris/
+ *
+ */
+
+#if HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#include "FourierTransform.h"
+#include "LorisExceptions.h"
+#include "Notifier.h"
+
+#include <cmath>
+#include <complex>
+
+#if defined(HAVE_M_PI) && (HAVE_M_PI)
+ const double Pi = M_PI;
+#else
+ const double Pi = std::acos( -1.0 );
+#endif
+
+#if defined(HAVE_FFTW3_H) && HAVE_FFTW3_H
+ #include <fftw3.h>
+#elif defined(HAVE_FFTW_H) && HAVE_FFTW_H
+ #include <fftw.h>
+#endif
+
+// ---------------------------------------------------------------------------
+// isPO2 - return true if N is a power of two
+// ---------------------------------------------------------------------------
+// If out_expon is non-zero, return the exponent in that address.
+//
+static bool isPO2( unsigned int N, int * out_expon = 0 )
+{
+ unsigned int M = 1;
+ int exp = 0;
+ while ( M < N )
+ {
+ M *= 2;
+ ++exp;
+ }
+ if ( 0 != out_expon && M == N )
+ {
+ *out_expon = exp;
+ }
+ return M == N;
+}
+
+// ===========================================================================
+// No longer matters that fftw and this class use the same floating point
+// data format. The insulating implementation class now does its job of
+// really insulating clients completely from FFTW, by copying data between
+// buffers of std::complex< double > and fftw_complex, rather than
+// relying on those two complex types to have the same memory layout.
+// The overhead of copying is probably not significant, compared to
+// the expense of computing the transform.
+//
+// about complex math functions for fftw_complex:
+//
+// These functions are all defined as templates in <complex>.
+// Regrettably, they are all implemented using real() and
+// imag() _member_ functions of the template argument, T.
+// If they had instead been implemented in terms of the real()
+// and imag() (template) free functions, then I could just specialize
+// those two for the fftw complex data type, and the other template
+// functions would work. Instead, I have to specialize _all_ of
+// those functions that I want to use. I hope this was a learning
+// experience for someone... In the mean time, the alternative I
+// have is to take advantage of the fact that fftw_complex and
+// std::complex<double> have the same footprint, so I can just
+// cast back and forth between the two types. Its icky, but it
+// works, and its a lot faster than converting, and more palatable
+// than redefining all those operators.
+//
+// On the subject of brilliant designs, fftw_complex is defined as
+// a typedef of an anonymous struct, as in typedef struct {...} fftw_complex,
+// so I cannot forward-declare that type.
+//
+// In other good news, the planning structure got a slight name change
+// in version 3, making it even more important to remove all traces of
+// FFTW from the FourierTransform class definition.
+//
+// ===========================================================================
+
+// begin namespace
+namespace Loris {
+
+using std::complex;
+using std::vector;
+
+// --- private implementation class ---
+
+// ---------------------------------------------------------------------------
+// FTimpl
+//
+// Insulating implementation class to insulate clients
+// completely from everything about the interaction between
+// Loris and FFTW. There is more copying of data between buffers,
+// but this is not the expensive part of computing Fourier transforms
+// and we don't have to do unsavory things that require std::complex
+// and fftw_complex to have the same memory layout (we could even get
+// by with single-precision floats in FFTW if necessary). Also got
+// rid of lots of shared buffer stuff that just made the implementation
+// lots more complicated than necessary. This one is simple, if not
+// as memory efficient.
+//
+
+#if defined(HAVE_FFTW3_H) && HAVE_FFTW3_H
+
+class FTimpl // FFTW version 3
+{
+private:
+
+ fftw_plan plan;
+ FourierTransform::size_type N;
+ fftw_complex * ftIn;
+ fftw_complex * ftOut;
+
+public:
+
+ // Construct an implementation instance:
+ // allocate an input buffer, and an output buffer
+ // and make a plan.
+ FTimpl( FourierTransform::size_type sz ) :
+ plan( 0 ), N( sz ), ftIn( 0 ), ftOut( 0 )
+ {
+ // allocate buffers:
+ ftIn = (fftw_complex *)fftw_malloc( sizeof( fftw_complex ) * N );
+ ftOut = (fftw_complex *)fftw_malloc( sizeof( fftw_complex ) * N );
+ if ( 0 == ftIn || 0 == ftOut )
+ {
+ fftw_free( ftIn );
+ fftw_free( ftOut );
+ throw RuntimeError( "cannot allocate Fourier transform buffers" );
+ }
+
+ // create a plan:
+ plan = fftw_plan_dft_1d( N, ftIn, ftOut, FFTW_FORWARD, FFTW_ESTIMATE );
+
+ // verify:
+ if ( 0 == plan )
+ {
+ Throw( RuntimeError, "FourierTransform could not make a (fftw) plan." );
+ }
+ }
+
+ // Destroy the implementation instance:
+ // dump the plan.
+ ~FTimpl( void )
+ {
+ if ( 0 != plan )
+ {
+ fftw_destroy_plan( plan );
+ }
+
+ fftw_free( ftIn );
+ fftw_free( ftOut );
+ }
+
+ // Copy complex< double >'s from a buffer into ftIn,
+ // the buffer must be as long as ftIn.
+ void loadInput( const complex< double > * bufPtr )
+ {
+ for ( FourierTransform::size_type k = 0; k < N; ++k )
+ {
+ ftIn[ k ][0] = bufPtr->real(); // real part
+ ftIn[ k ][1] = bufPtr->imag(); // imaginary part
+ ++bufPtr;
+ }
+ }
+
+ // Copy complex< double >'s from ftOut into a buffer,
+ // which must be as long as ftOut.
+ void copyOutput( complex< double > * bufPtr ) const
+ {
+ for ( FourierTransform::size_type k = 0; k < N; ++k )
+ {
+ *bufPtr = complex< double >( ftOut[ k ][0], ftOut[ k ][1] );
+ ++bufPtr;
+ }
+ }
+
+ // Compute a forward transform.
+ void forward( void )
+ {
+ fftw_execute( plan );
+ }
+
+}; // end of class FTimpl for FFTW version 3
+
+#elif defined(HAVE_FFTW_H) && HAVE_FFTW_H
+
+// "die hook" for FFTW, which otherwise try to write to a
+// non-existent console.
+static void fftw_die_Loris( const char * s )
+{
+ using namespace std; // exit might be hidden in there
+
+ notifier << "The FFTW library used by Loris has encountered a fatal error: " << s << endl;
+ exit(EXIT_FAILURE);
+}
+
+class FTimpl // FFTW version 2
+{
+private:
+
+ fftw_plan plan;
+ FourierTransform::size_type N;
+ fftw_complex * ftIn;
+ fftw_complex * ftOut;
+
+public:
+
+ // Construct an implementation instance:
+ // allocate an input buffer, and an output buffer
+ // and make a plan.
+ FTimpl( FourierTransform::size_type sz ) :
+ plan( 0 ), N( sz ), ftIn( 0 ), ftOut( 0 )
+ {
+ // allocate buffers:
+ ftIn = (fftw_complex *)fftw_malloc( sizeof( fftw_complex ) * N );
+ ftOut = (fftw_complex *)fftw_malloc( sizeof( fftw_complex ) * N );
+ if ( 0 == ftIn || 0 == ftOut )
+ {
+ fftw_free( ftIn );
+ fftw_free( ftOut );
+ Throw( RuntimeError, "cannot allocate Fourier transform buffers" );
+ }
+
+ // create a plan:
+ plan = fftw_create_plan_specific( N, FFTW_FORWARD, FFTW_ESTIMATE,
+ ftIn, 1, ftOut, 1 );
+
+ // verify:
+ if ( 0 == plan )
+ {
+ Throw( RuntimeError, "FourierTransform could not make a (fftw) plan." );
+ }
+
+ // FFTW calls fprintf a lot, which may be a problem in
+ // non-console-enabled applications. Catch fftw_die()
+ // calls by routing the error message to our own Notifier
+ // and exiting, using the function defined above.
+ //
+ // (version 2 only)
+ fftw_die_hook = fftw_die_Loris;
+ }
+
+ // Destroy the implementation instance:
+ // dump the plan.
+ ~FTimpl( void )
+ {
+ if ( 0 != plan )
+ {
+ fftw_destroy_plan( plan );
+ }
+
+ fftw_free( ftIn );
+ fftw_free( ftOut );
+ }
+
+ // Copy complex< double >'s from a buffer into ftIn,
+ // the buffer must be as long as ftIn.
+ void loadInput( const complex< double > * bufPtr )
+ {
+ for ( FourierTransform::size_type k = 0; k < N; ++k )
+ {
+ c_re( ftIn[ k ] ) = bufPtr->real();
+ c_im( ftIn[ k ] ) = bufPtr->imag();
+ ++bufPtr;
+ }
+ }
+
+ // Copy complex< double >'s from ftOut into a buffer,
+ // which must be as long as ftOut.
+ void copyOutput( complex< double > * bufPtr ) const
+ {
+ for ( FourierTransform::size_type k = 0; k < N; ++k )
+ {
+ *bufPtr = complex< double >( c_re( ftOut[ k ] ), c_im( ftOut[ k ] ) );
+ ++bufPtr;
+ }
+ }
+
+ // Compute a forward transform.
+ void forward( void )
+ {
+ fftw_one( plan, ftIn, ftOut );
+ }
+
+}; // end of class FTimpl for FFTW version 2
+
+#else
+
+#define SORRY_NO_FFTW 1
+
+// function prototype, definition in fftsg.c
+extern "C" void cdft(int, int, double *, int *, double *);
+
+// function prototype, definition below
+static void slowDFT( double * in, double * out, int N );
+
+// Uses General Purpose FFT (Fast Fourier/Cosine/Sine Transform) Package
+// by Takuya OOURA, http://momonga.t.u-tokyo.ac.jp/~ooura/fft.html defined
+// in fftsg.c.
+//
+// In the event that the size is not a power of two, uses a (very) slow
+// direct DFT computation, defined below. In this case, the workspace
+// array is not used, and the twiddle factor array is used to store the
+// transform result.
+
+class FTimpl // platform-neutral stand-alone implementation
+{
+private:
+
+ double * mTxInOut; // input/output buffer for in-place transform
+ double * mTwiddle; // storage for twiddle factors
+ int * mWorkspace; // workspace storage
+
+ FourierTransform::size_type N;
+
+ bool mIsPO2;
+
+public:
+
+ // Construct an implementation instance:
+ // allocate buffers and workspace, and
+ // initialize the twiddle factors.
+ FTimpl( FourierTransform::size_type sz ) :
+ mTxInOut( 0 ), mTwiddle( 0 ), mWorkspace( 0 ), N( sz ), mIsPO2( isPO2( sz ) )
+ {
+ mTxInOut = new double[ 2*N ];
+ // input/output buffer for in-place transform
+
+ if ( mIsPO2 )
+ {
+ mTwiddle = new double[ N/2 ];
+ // storage for twiddle factors
+
+ mWorkspace = new int[ 2*int( std::sqrt((double)N) + 0.5 ) ];
+ // workspace
+
+ if ( 0 == mTxInOut || 0 == mTwiddle || 0 == mWorkspace )
+ {
+ delete [] mTxInOut;
+ delete [] mTwiddle;
+ delete [] mWorkspace;
+ Throw( RuntimeError, "FourierTransform: could not initialize tranform" );
+ }
+
+ mWorkspace[0] = 0; // first time only, triggers setup
+ // no need to do it now, it will happen the
+ // first time a transform is computed
+ // cdft_double( 2*N, -1, mTxInOut, mWorkspace, mTwiddle );
+ }
+ else
+ {
+ mTwiddle = new double[ 2*N ];
+ // use for result in slowDFT
+
+ if ( 0 == mTxInOut || 0 == mTwiddle )
+ {
+ delete [] mTxInOut;
+ delete [] mTwiddle;
+ Throw( RuntimeError, "FourierTransform: could not initialize tranform" );
+ }
+ }
+ }
+
+ // Destroy the implementation instance:
+ ~FTimpl( void )
+ {
+ delete [] mTxInOut;
+ delete [] mTwiddle;
+ delete [] mWorkspace;
+ }
+
+ // Copy complex< double >'s from a buffer into ftIn,
+ // the buffer must be as long as ftIn.
+ void loadInput( const complex< double > * bufPtr )
+ {
+ for ( FourierTransform::size_type k = 0; k < N; ++k )
+ {
+ mTxInOut[ 2*k ] = bufPtr->real();
+ mTxInOut[ 2*k+1 ] = bufPtr->imag();
+ ++bufPtr;
+ }
+ }
+
+ // Copy complex< double >'s from ftOut into a buffer,
+ // which must be as long as ftOut. Result might be
+ // stored in the twiddle factor array if this is not
+ // power of two length DFT.
+ void copyOutput( complex< double > * bufPtr ) const
+ {
+ double * result = mTxInOut;
+ if ( !mIsPO2 )
+ {
+ result = mTwiddle;
+ }
+
+ for ( FourierTransform::size_type k = 0; k < N; ++k )
+ {
+ *bufPtr = complex< double >( result[ 2*k ], result[ 2*k+1 ] );
+ ++bufPtr;
+ }
+ }
+
+ // Compute a forward transform.
+ void forward( void )
+ {
+ if ( mIsPO2 )
+ {
+ cdft( 2*N, -1, mTxInOut, mWorkspace, mTwiddle );
+ }
+ else
+ {
+ slowDFT( mTxInOut, mTwiddle, N );
+ }
+ }
+
+}; // end of class platform-neutral stand-alone FTimpl
+
+#endif
+
+// --- FourierTransform members ---
+
+// ---------------------------------------------------------------------------
+// FourierTransform constructor
+// ---------------------------------------------------------------------------
+//! Initialize a new FourierTransform of the specified size.
+//!
+//! \param len is the length of the transform in samples (the
+//! number of samples in the transform)
+//! \throw RuntimeError if the necessary buffers cannot be
+//! allocated, or there is an error configuring FFTW.
+//
+FourierTransform::FourierTransform( size_type len ) :
+ _buffer( len ),
+ _impl( new FTimpl( len ) )
+{
+ // zero:
+ std::fill( _buffer.begin(), _buffer.end(), 0. );
+}
+
+// ---------------------------------------------------------------------------
+// FourierTransform copy constructor
+// ---------------------------------------------------------------------------
+//! Initialize a new FourierTransform that is a copy of another,
+//! having the same size and the same buffer contents.
+//!
+//! \param rhs is the instance to copy
+//! \throw RuntimeError if the necessary buffers cannot be
+//! allocated, or there is an error configuring FFTW.
+//
+FourierTransform::FourierTransform( const FourierTransform & rhs ) :
+ _buffer( rhs._buffer ),
+ _impl( new FTimpl( rhs._buffer.size() ) ) // not copied
+{
+}
+
+// ---------------------------------------------------------------------------
+// FourierTransform destructor
+// ---------------------------------------------------------------------------
+//! Free the resources associated with this FourierTransform.
+//
+FourierTransform::~FourierTransform( void )
+{
+ delete _impl;
+}
+
+// ---------------------------------------------------------------------------
+// FourierTransform assignment operator
+// ---------------------------------------------------------------------------
+//! Make this FourierTransform a copy of another, having
+//! the same size and buffer contents.
+//!
+//! \param rhs is the instance to copy
+//! \return a refernce to this instance
+//! \throw RuntimeError if the necessary buffers cannot be
+//! allocated, or there is an error configuring FFTW.
+//
+FourierTransform &
+FourierTransform::operator=( const FourierTransform & rhs )
+{
+ if ( this != &rhs )
+ {
+ _buffer = rhs._buffer;
+
+ // The implementation instance is not assigned,
+ // but a new one is created.
+ delete _impl;
+ _impl = 0;
+ _impl = new FTimpl( _buffer.size() );
+ }
+
+ return *this;
+}
+
+// ---------------------------------------------------------------------------
+// size
+// ---------------------------------------------------------------------------
+//! Return the length of the transform (in samples).
+//!
+//! \return the length of the transform in samples.
+FourierTransform::size_type
+FourierTransform::size( void ) const
+{
+ return _buffer.size();
+}
+
+// ---------------------------------------------------------------------------
+// transform
+// ---------------------------------------------------------------------------
+//! Compute the Fourier transform of the samples stored in the
+//! transform buffer. The samples stored in the transform buffer
+//! (accessed by index or by iterator) are replaced by the
+//! transformed samples, in-place.
+//
+void
+FourierTransform::transform( void )
+{
+ // copy data into the transform input buffer:
+ _impl->loadInput( &_buffer.front() );
+
+ // crunch:
+ _impl->forward();
+
+ // copy the data out of the transform output buffer:
+ _impl->copyOutput( &_buffer.front() );
+}
+
+
+// --- slow non-power-of-two DFT implementation ---
+
+#if defined(SORRY_NO_FFTW)
+
+// ---------------------------------------------------------------------------
+// slowDFT
+// ---------------------------------------------------------------------------
+// Non-power-of-two DFT implementation. in and out cannot be the same,
+// and each is 2*N long, storing interleaved complex numbers.
+// This version is only used when FFTW is unavailable.
+//
+static
+void slowDFT( double * in, double * out, int N )
+{
+#if 1
+ // slow DFT
+ // This version of the direct DFT computation is tolerably
+ // speedy, even for rather long transforms (like 8k).
+ // There is only one expensive call to std::polar, and
+ // twiddle factors are updated by multiplying. This
+ // causes some loss in numerical accuracy, worse for
+ // longer transforms, but even for 10k long transforms,
+ // accuracy is within hundredths of a percent in my experiments.
+
+ const std::complex< double > eminj2pioN =
+ std::polar( 1.0, -2.0 * Pi / N );
+
+ std::complex< double > Wn = 1;
+ for ( int n = 0; n < N; ++n )
+ {
+ std::complex< double > Wkn = 1;
+ std::complex< double > Xkn = 0;
+ for ( int k = 0; k < N; ++k )
+ {
+ Xkn += std::complex< double >( in[ 2*k ], in[ 2*k+1 ] ) * Wkn;
+ Wkn *= Wn;
+ }
+
+ out[ 2*n ] = Xkn.real();
+ out[ 2*n+1 ] = Xkn.imag();
+ Wn *= eminj2pioN;
+ }
+
+#else
+ // very, very slow
+ // This version of the direct DFT computation is slightly
+ // more accurate, increasingly so for longer transforms,
+ // but it is so much slower (4-5x) that the small improvement
+ // in accuracy is probably not worth the extra wait. For
+ // short transforms, the accuracy of both algorithms is
+ // very high, and for long transforms, this algorithm is
+ // too slow.
+ //
+ // Both algorithms are retained here, so that this
+ // tradeoff can be re-evaluated if necessary.
+
+ for ( int n = 0; n < N; ++n )
+ {
+ std::complex< double > Xkn = 0;
+ for ( int k = 0; k < N; ++k )
+ {
+ std::complex< double > Wkn = std::polar( 1.0, -2.0 * Pi * k * n / N );
+ Xkn += std::complex< double >( in[ 2*k ], in[ 2*k+1 ] ) * Wkn;
+ }
+
+ out[ 2*n ] = Xkn.real();
+ out[ 2*n+1 ] = Xkn.imag();
+ }
+
+#endif
+}
+
+#endif // defined(SORRY_NO_FFTW)
+
+} // end of namespace Loris