Click here to Skip to main content
15,886,104 members
Articles / Desktop Programming / WPF

Duplicate songs detector via audio fingerprinting

Rate me:
Please Sign up or sign in to vote.
4.96/5 (337 votes)
23 Jun 2020MIT44 min read 1.3M   20.4K   533  
Explains sound fingerprinting algorithm, with a practical example of detecting duplicate files on the user's local drive.
The aim of this article is to show an efficient algorithm of signal processing which will allow one to have a competent system of sound fingerprinting and signal recognition. I'll try to come with some explanations of the article's algorithm, and also speak about how it can be implemented using the C# programming language. Additionally, I'll try to cover topics of digital signal processing that are used in the algorithm, thus you'll be able to get a clearer image of the entire system. And as a proof of concept, I'll show you how to develop a simple WPF MVVM application.
/*
 * BSD Licence:
 * Copyright (c) 2001, 2002 Ben Houston [ ben@exocortex.org ]
 * Exocortex Technologies [ www.exocortex.org ]
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, 
 * this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the following disclaimer in the 
 * documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the <ORGANIZATION> nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

using System;
using System.Diagnostics;

//using Exocortex.Imaging;

namespace DuplicateTracks.Fingerprinting.MathUtils
{
    // Comments? Questions? Bugs? Tell Ben Houston at ben@exocortex.org
    // Version: May 4, 2002

    /// <summary>
    ///   <p>Static functions for doing various Fourier Operations.</p>
    /// </summary>
    public class Fourier
    {
        //======================================================================================

        private const int cMaxLength = 4096;
        private const int cMinLength = 1;

        private const int cMaxBits = 12;
        private const int cMinBits = 0;
        private static readonly int[][] _reversedBits = new int[cMaxBits][];
        private static int[][] _reverseBits;
        private static int _lookupTabletLength = -1;
        private static double[,][] _uRLookup;
        private static double[,][] _uILookup;
        private static float[,][] _uRLookupF;
        private static float[,][] _uILookupF;
        private static bool _bufferFLocked;
        private static float[] _bufferF = new float[0];
        private static bool _bufferCFLocked;
        private static ComplexF[] _bufferCF = new ComplexF[0];
        private static bool _bufferCLocked;
        private static Complex[] _bufferC = new Complex[0];

        private Fourier()
        {
        }

        //======================================================================================

        private static void Swap(ref float a, ref float b)
        {
            float temp = a;
            a = b;
            b = temp;
        }

        private static void Swap(ref double a, ref double b)
        {
            double temp = a;
            a = b;
            b = temp;
        }

        private static void Swap(ref ComplexF a, ref ComplexF b)
        {
            ComplexF temp = a;
            a = b;
            b = temp;
        }

        private static void Swap(ref Complex a, ref Complex b)
        {
            Complex temp = a;
            a = b;
            b = temp;
        }

        //-------------------------------------------------------------------------------------


        private static bool IsPowerOf2(int x)
        {
            return (x & (x - 1)) == 0;
            //return	( x == Pow2( Log2( x ) ) );
        }

        private static int Pow2(int exponent)
        {
            if (exponent >= 0 && exponent < 31)
            {
                return 1 << exponent;
            }
            return 0;
        }

        private static int Log2(int x)
        {
            if (x <= 65536)
            {
                if (x <= 256)
                {
                    if (x <= 16)
                    {
                        if (x <= 4)
                        {
                            if (x <= 2)
                            {
                                if (x <= 1)
                                {
                                    return 0;
                                }
                                return 1;
                            }
                            return 2;
                        }
                        if (x <= 8)
                            return 3;
                        return 4;
                    }
                    if (x <= 64)
                    {
                        if (x <= 32)
                            return 5;
                        return 6;
                    }
                    if (x <= 128)
                        return 7;
                    return 8;
                }
                if (x <= 4096)
                {
                    if (x <= 1024)
                    {
                        if (x <= 512)
                            return 9;
                        return 10;
                    }
                    if (x <= 2048)
                        return 11;
                    return 12;
                }
                if (x <= 16384)
                {
                    if (x <= 8192)
                        return 13;
                    return 14;
                }
                if (x <= 32768)
                    return 15;
                return 16;
            }
            if (x <= 16777216)
            {
                if (x <= 1048576)
                {
                    if (x <= 262144)
                    {
                        if (x <= 131072)
                            return 17;
                        return 18;
                    }
                    if (x <= 524288)
                        return 19;
                    return 20;
                }
                if (x <= 4194304)
                {
                    if (x <= 2097152)
                        return 21;
                    return 22;
                }
                if (x <= 8388608)
                    return 23;
                return 24;
            }
            if (x <= 268435456)
            {
                if (x <= 67108864)
                {
                    if (x <= 33554432)
                        return 25;
                    return 26;
                }
                if (x <= 134217728)
                    return 27;
                return 28;
            }
            if (x <= 1073741824)
            {
                if (x <= 536870912)
                    return 29;
                return 30;
            }
            //	since int is unsigned it can never be higher than 2,147,483,647
            //	if( x <= 2147483648 )
            //		return	31;	
            //	return	32;	
            return 31;
        }

        //-------------------------------------------------------------------------------------
        //-------------------------------------------------------------------------------------

        private static int ReverseBits(int index, int numberOfBits)
        {
            Debug.Assert(numberOfBits >= cMinBits);
            Debug.Assert(numberOfBits <= cMaxBits);

            int reversedIndex = 0;
            for (int i = 0; i < numberOfBits; i++)
            {
                reversedIndex = (reversedIndex << 1) | (index & 1);
                index = (index >> 1);
            }
            return reversedIndex;
        }

        //-------------------------------------------------------------------------------------

        private static int[] GetReversedBits(int numberOfBits)
        {
            Debug.Assert(numberOfBits >= cMinBits);
            Debug.Assert(numberOfBits <= cMaxBits);
            if (_reversedBits[numberOfBits - 1] == null)
            {
                int maxBits = Pow2(numberOfBits);
                int[] reversedBits = new int[maxBits];
                for (int i = 0; i < maxBits; i++)
                {
                    int oldBits = i;
                    int newBits = 0;
                    for (int j = 0; j < numberOfBits; j++)
                    {
                        newBits = (newBits << 1) | (oldBits & 1);
                        oldBits = (oldBits >> 1);
                    }
                    reversedBits[i] = newBits;
                }
                _reversedBits[numberOfBits - 1] = reversedBits;
            }
            return _reversedBits[numberOfBits - 1];
        }

        //-------------------------------------------------------------------------------------

        private static void ReorderArray(float[] data)
        {
            Debug.Assert(data != null);

            int length = data.Length/2;

            Debug.Assert(IsPowerOf2(length));
            Debug.Assert(length >= cMinLength);
            Debug.Assert(length <= cMaxLength);

            int[] reversedBits = GetReversedBits(Log2(length));
            for (int i = 0; i < length; i++)
            {
                int swap = reversedBits[i];
                if (swap > i)
                {
                    Swap(ref data[(i << 1)], ref data[(swap << 1)]);
                    Swap(ref data[(i << 1) + 1], ref data[(swap << 1) + 1]);
                }
            }
        }

        private static void ReorderArray(double[] data)
        {
            Debug.Assert(data != null);

            int length = data.Length/2;

            Debug.Assert(IsPowerOf2(length));
            Debug.Assert(length >= cMinLength);
            Debug.Assert(length <= cMaxLength);

            int[] reversedBits = GetReversedBits(Log2(length));
            for (int i = 0; i < length; i++)
            {
                int swap = reversedBits[i];
                if (swap > i)
                {
                    Swap(ref data[i << 1], ref data[swap << 1]);
                    Swap(ref data[i << 1 + 1], ref data[swap << 1 + 1]);
                }
            }
        }

        private static void ReorderArray(Complex[] data)
        {
            Debug.Assert(data != null);

            int length = data.Length;

            Debug.Assert(IsPowerOf2(length));
            Debug.Assert(length >= cMinLength);
            Debug.Assert(length <= cMaxLength);

            int[] reversedBits = GetReversedBits(Log2(length));
            for (int i = 0; i < length; i++)
            {
                int swap = reversedBits[i];
                if (swap > i)
                {
                    Complex temp = data[i];
                    data[i] = data[swap];
                    data[swap] = temp;
                }
            }
        }

        private static void ReorderArray(ComplexF[] data)
        {
            Debug.Assert(data != null);

            int length = data.Length;

            Debug.Assert(IsPowerOf2(length));
            Debug.Assert(length >= cMinLength);
            Debug.Assert(length <= cMaxLength);

            int[] reversedBits = GetReversedBits(Log2(length));
            for (int i = 0; i < length; i++)
            {
                int swap = reversedBits[i];
                if (swap > i)
                {
                    ComplexF temp = data[i];
                    data[i] = data[swap];
                    data[swap] = temp;
                }
            }
        }

        //======================================================================================

        private static int _ReverseBits(int bits, int n)
        {
            int bitsReversed = 0;
            for (int i = 0; i < n; i++)
            {
                bitsReversed = (bitsReversed << 1) | (bits & 1);
                bits = (bits >> 1);
            }
            return bitsReversed;
        }

        private static void InitializeReverseBits(int levels)
        {
            _reverseBits = new int[levels + 1][];
            for (int j = 0; j < (levels + 1); j++)
            {
                int count = (int) Math.Pow(2, j);
                _reverseBits[j] = new int[count];
                for (int i = 0; i < count; i++)
                {
                    _reverseBits[j][i] = _ReverseBits(i, j);
                }
            }
        }

        private static void SyncLookupTableLength(int length)
        {
            Debug.Assert(length < 1024*10);
            Debug.Assert(length >= 0);
            if (length > _lookupTabletLength)
            {
                int level = (int) Math.Ceiling(Math.Log(length, 2));
                InitializeReverseBits(level);
                InitializeComplexRotations(level);
                //_cFFTData	= new Complex[ Math2.CeilingBase( length, 2 ) ];
                //_cFFTDataF	= new ComplexF[ Math2.CeilingBase( length, 2 ) ];
                _lookupTabletLength = length;
            }
        }

        private static int GetLookupTableLength()
        {
            return _lookupTabletLength;
        }

        private static void ClearLookupTables()
        {
            _uRLookup = null;
            _uILookup = null;
            _uRLookupF = null;
            _uILookupF = null;
            _lookupTabletLength = -1;
        }

        private static void InitializeComplexRotations(int levels)
        {
            int ln = levels;
            //_wRLookup = new float[ levels + 1, 2 ];
            //_wILookup = new float[ levels + 1, 2 ];

            _uRLookup = new double[levels + 1,2][];
            _uILookup = new double[levels + 1,2][];

            _uRLookupF = new float[levels + 1,2][];
            _uILookupF = new float[levels + 1,2][];

            int N = 1;
            for (int level = 1; level <= ln; level++)
            {
                int M = N;
                N <<= 1;

                //float scale = (float)( 1 / Math.Sqrt( 1 << ln ) );

                // positive sign ( i.e. [M,0] )
                {
                    double uR = 1;
                    double uI = 0;
                    double angle = Math.PI/M*1;
                    double wR = Math.Cos(angle);
                    double wI = Math.Sin(angle);

                    _uRLookup[level, 0] = new double[M];
                    _uILookup[level, 0] = new double[M];
                    _uRLookupF[level, 0] = new float[M];
                    _uILookupF[level, 0] = new float[M];

                    for (int j = 0; j < M; j++)
                    {
                        _uRLookupF[level, 0][j] = (float) (_uRLookup[level, 0][j] = uR);
                        _uILookupF[level, 0][j] = (float) (_uILookup[level, 0][j] = uI);
                        double uwI = uR*wI + uI*wR;
                        uR = uR*wR - uI*wI;
                        uI = uwI;
                    }
                }
                {
                    // negative sign ( i.e. [M,1] )
                    double uR = 1;
                    double uI = 0;
                    double angle = Math.PI/M*-1;
                    double wR = Math.Cos(angle);
                    double wI = Math.Sin(angle);

                    _uRLookup[level, 1] = new double[M];
                    _uILookup[level, 1] = new double[M];
                    _uRLookupF[level, 1] = new float[M];
                    _uILookupF[level, 1] = new float[M];

                    for (int j = 0; j < M; j++)
                    {
                        _uRLookupF[level, 1][j] = (float) (_uRLookup[level, 1][j] = uR);
                        _uILookupF[level, 1][j] = (float) (_uILookup[level, 1][j] = uI);
                        double uwI = uR*wI + uI*wR;
                        uR = uR*wR - uI*wI;
                        uI = uwI;
                    }
                }
            }
        }

        //======================================================================================
        //======================================================================================

        private static void LockBufferF(int length, ref float[] buffer)
        {
            Debug.Assert(_bufferFLocked == false);
            _bufferFLocked = true;
            if (length >= _bufferF.Length)
            {
                _bufferF = new float[length];
            }
            buffer = _bufferF;
        }

        private static void UnlockBufferF(ref float[] buffer)
        {
            Debug.Assert(_bufferF == buffer);
            Debug.Assert(_bufferFLocked);
            _bufferFLocked = false;
            buffer = null;
        }

        private static void LinearFFT(float[] data, int start, int inc, int length, FourierDirection direction)
        {
            Debug.Assert(data != null);
            Debug.Assert(start >= 0);
            Debug.Assert(inc >= 1);
            Debug.Assert(length >= 1);
            Debug.Assert((start + inc*(length - 1))*2 < data.Length);

            // copy to buffer
            float[] buffer = null;
            LockBufferF(length*2, ref buffer);
            int j = start;
            for (int i = 0; i < length*2; i++)
            {
                buffer[i] = data[j];
                j += inc;
            }

            FFT(buffer, length, direction);

            // copy from buffer
            j = start;
            for (int i = 0; i < length; i++)
            {
                data[j] = buffer[i];
                j += inc;
            }
            UnlockBufferF(ref buffer);
        }

        private static void LinearFFT_Quick(float[] data, int start, int inc, int length, FourierDirection direction)
        {
            /*Debug.Assert( data != null );
            Debug.Assert( start >= 0 );
            Debug.Assert( inc >= 1 );
            Debug.Assert( length >= 1 );
            Debug.Assert( ( start + inc * ( length - 1 ) ) * 2 < data.Length );*/

            // copy to buffer
            float[] buffer = null;
            LockBufferF(length*2, ref buffer);
            int j = start;
            for (int i = 0; i < length*2; i++)
            {
                buffer[i] = data[j];
                j += inc;
            }

            FFT_Quick(buffer, length, direction);

            // copy from buffer
            j = start;
            for (int i = 0; i < length; i++)
            {
                data[j] = buffer[i];
                j += inc;
            }
            UnlockBufferF(ref buffer);
        }

        //======================================================================================
        //======================================================================================

        private static void LockBufferCF(int length, ref ComplexF[] buffer)
        {
            Debug.Assert(length >= 0);
            Debug.Assert(_bufferCFLocked == false);

            _bufferCFLocked = true;
            if (length != _bufferCF.Length)
            {
                _bufferCF = new ComplexF[length];
            }
            buffer = _bufferCF;
        }

        private static void UnlockBufferCF(ref ComplexF[] buffer)
        {
            Debug.Assert(_bufferCF == buffer);
            Debug.Assert(_bufferCFLocked);

            _bufferCFLocked = false;
            buffer = null;
        }

        private static void LinearFFT(ComplexF[] data, int start, int inc, int length, FourierDirection direction)
        {
            Debug.Assert(data != null);
            Debug.Assert(start >= 0);
            Debug.Assert(inc >= 1);
            Debug.Assert(length >= 1);
            Debug.Assert((start + inc*(length - 1)) < data.Length);

            // copy to buffer
            ComplexF[] buffer = null;
            LockBufferCF(length, ref buffer);
            int j = start;
            for (int i = 0; i < length; i++)
            {
                buffer[i] = data[j];
                j += inc;
            }

            FFT(buffer, length, direction);

            // copy from buffer
            j = start;
            for (int i = 0; i < length; i++)
            {
                data[j] = buffer[i];
                j += inc;
            }
            UnlockBufferCF(ref buffer);
        }

        private static void LinearFFT_Quick(ComplexF[] data, int start, int inc, int length, FourierDirection direction)
        {
            /*Debug.Assert( data != null );
            Debug.Assert( start >= 0 );
            Debug.Assert( inc >= 1 );
            Debug.Assert( length >= 1 );
            Debug.Assert( ( start + inc * ( length - 1 ) ) < data.Length );	*/

            // copy to buffer
            ComplexF[] buffer = null;
            LockBufferCF(length, ref buffer);
            int j = start;
            for (int i = 0; i < length; i++)
            {
                buffer[i] = data[j];
                j += inc;
            }

            FFT(buffer, length, direction);

            // copy from buffer
            j = start;
            for (int i = 0; i < length; i++)
            {
                data[j] = buffer[i];
                j += inc;
            }
            UnlockBufferCF(ref buffer);
        }

        //======================================================================================
        //======================================================================================

        private static void LockBufferC(int length, ref Complex[] buffer)
        {
            Debug.Assert(length >= 0);
            Debug.Assert(_bufferCLocked == false);

            _bufferCLocked = true;
            if (length >= _bufferC.Length)
            {
                _bufferC = new Complex[length];
            }
            buffer = _bufferC;
        }

        private static void UnlockBufferC(ref Complex[] buffer)
        {
            Debug.Assert(_bufferC == buffer);
            Debug.Assert(_bufferCLocked);

            _bufferCLocked = false;
            buffer = null;
        }

        private static void LinearFFT(Complex[] data, int start, int inc, int length, FourierDirection direction)
        {
            Debug.Assert(data != null);
            Debug.Assert(start >= 0);
            Debug.Assert(inc >= 1);
            Debug.Assert(length >= 1);
            Debug.Assert((start + inc*(length - 1)) < data.Length);

            // copy to buffer
            Complex[] buffer = null;
            LockBufferC(length, ref buffer);
            int j = start;
            for (int i = 0; i < length; i++)
            {
                buffer[i] = data[j];
                j += inc;
            }

            FFT(buffer, length, direction);

            // copy from buffer
            j = start;
            for (int i = 0; i < length; i++)
            {
                data[j] = buffer[i];
                j += inc;
            }
            UnlockBufferC(ref buffer);
        }

        private static void LinearFFT_Quick(Complex[] data, int start, int inc, int length, FourierDirection direction)
        {
            /*Debug.Assert( data != null );
            Debug.Assert( start >= 0 );
            Debug.Assert( inc >= 1 );
            Debug.Assert( length >= 1 );
            Debug.Assert( ( start + inc * ( length - 1 ) ) < data.Length );*/

            // copy to buffer
            Complex[] buffer = null;
            LockBufferC(length, ref buffer);
            int j = start;
            for (int i = 0; i < length; i++)
            {
                buffer[i] = data[j];
                j += inc;
            }

            FFT_Quick(buffer, length, direction);

            // copy from buffer
            j = start;
            for (int i = 0; i < length; i++)
            {
                data[j] = buffer[i];
                j += inc;
            }
            UnlockBufferC(ref buffer);
        }

        //======================================================================================
        //======================================================================================

        /// <summary>
        ///   Compute a 1D fast Fourier transform of a dataset of complex numbers (as pairs of float's).
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "length"></param>
        /// <param name = "direction"></param>
        public static void FFT(float[] data, int length, FourierDirection direction)
        {
            Debug.Assert(data != null);
            Debug.Assert(data.Length >= length*2);
            Debug.Assert(IsPowerOf2(length));

            SyncLookupTableLength(length);

            int ln = Log2(length);

            // reorder array
            ReorderArray(data);

            // successive doubling
            int N = 1;
            int signIndex = (direction == FourierDirection.Forward) ? 0 : 1;
            for (int level = 1; level <= ln; level++)
            {
                int M = N;
                N <<= 1;

                float[] uRLookup = _uRLookupF[level, signIndex];
                float[] uILookup = _uILookupF[level, signIndex];

                for (int j = 0; j < M; j++)
                {
                    float uR = uRLookup[j];
                    float uI = uILookup[j];

                    for (int evenT = j; evenT < length; evenT += N)
                    {
                        int even = evenT << 1;
                        int odd = (evenT + M) << 1;

                        float r = data[odd];
                        float i = data[odd + 1];

                        float odduR = r*uR - i*uI;
                        float odduI = r*uI + i*uR;

                        r = data[even];
                        i = data[even + 1];

                        data[even] = r + odduR;
                        data[even + 1] = i + odduI;

                        data[odd] = r - odduR;
                        data[odd + 1] = i - odduI;
                    }
                }
            }
        }

        /// <summary>
        ///   Compute a 1D fast Fourier transform of a dataset of complex numbers (as pairs of float's).
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "length"></param>
        /// <param name = "direction"></param>
        public static void FFT_Quick(float[] data, int length, FourierDirection direction)
        {
            /*Debug.Assert( data != null );
            Debug.Assert( data.Length >= length*2 );
            Debug.Assert( Fourier.IsPowerOf2( length ) == true );

            Fourier.SyncLookupTableLength( length );*/

            int ln = Log2(length);

            // reorder array
            ReorderArray(data);

            // successive doubling
            int N = 1;
            int signIndex = (direction == FourierDirection.Forward) ? 0 : 1;
            for (int level = 1; level <= ln; level++)
            {
                int M = N;
                N <<= 1;

                float[] uRLookup = _uRLookupF[level, signIndex];
                float[] uILookup = _uILookupF[level, signIndex];

                for (int j = 0; j < M; j++)
                {
                    float uR = uRLookup[j];
                    float uI = uILookup[j];

                    for (int evenT = j; evenT < length; evenT += N)
                    {
                        int even = evenT << 1;
                        int odd = (evenT + M) << 1;

                        float r = data[odd];
                        float i = data[odd + 1];

                        float odduR = r*uR - i*uI;
                        float odduI = r*uI + i*uR;

                        r = data[even];
                        i = data[even + 1];

                        data[even] = r + odduR;
                        data[even + 1] = i + odduI;

                        data[odd] = r - odduR;
                        data[odd + 1] = i - odduI;
                    }
                }
            }
        }

        /// <summary>
        ///   Compute a 1D fast Fourier transform of a dataset of complex numbers.
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "length"></param>
        /// <param name = "direction"></param>
        public static void FFT(ComplexF[] data, int length, FourierDirection direction)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            if (data.Length < length)
            {
                throw new ArgumentOutOfRangeException("length", length, "must be at least as large as 'data.Length' parameter");
            }
            if (IsPowerOf2(length) == false)
            {
                throw new ArgumentOutOfRangeException("length", length, "must be a power of 2");
            }

            SyncLookupTableLength(length);

            int ln = Log2(length);

            // reorder array
            ReorderArray(data);

            // successive doubling
            int N = 1;
            int signIndex = (direction == FourierDirection.Forward) ? 0 : 1;

            for (int level = 1; level <= ln; level++)
            {
                int M = N;
                N <<= 1;

                float[] uRLookup = _uRLookupF[level, signIndex];
                float[] uILookup = _uILookupF[level, signIndex];

                for (int j = 0; j < M; j++)
                {
                    float uR = uRLookup[j];
                    float uI = uILookup[j];

                    for (int even = j; even < length; even += N)
                    {
                        int odd = even + M;

                        float r = data[odd].Re;
                        float i = data[odd].Im;

                        float odduR = r*uR - i*uI;
                        float odduI = r*uI + i*uR;

                        r = data[even].Re;
                        i = data[even].Im;

                        data[even].Re = r + odduR;
                        data[even].Im = i + odduI;

                        data[odd].Re = r - odduR;
                        data[odd].Im = i - odduI;
                    }
                }
            }
        }

        /// <summary>
        ///   Compute a 1D fast Fourier transform of a dataset of complex numbers.
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "length"></param>
        /// <param name = "direction"></param>
        public static void FFT_Quick(ComplexF[] data, int length, FourierDirection direction)
        {
            /*if( data == null ) {
                throw new ArgumentNullException( "data" );
            }
            if( data.Length < length ) {
                throw new ArgumentOutOfRangeException( "length", length, "must be at least as large as 'data.Length' parameter" );
            }
            if( Fourier.IsPowerOf2( length ) == false ) {
                throw new ArgumentOutOfRangeException( "length", length, "must be a power of 2" );
            }

            Fourier.SyncLookupTableLength( length );*/

            int ln = Log2(length);

            // reorder array
            ReorderArray(data);

            // successive doubling
            int N = 1;
            int signIndex = (direction == FourierDirection.Forward) ? 0 : 1;

            for (int level = 1; level <= ln; level++)
            {
                int M = N;
                N <<= 1;

                float[] uRLookup = _uRLookupF[level, signIndex];
                float[] uILookup = _uILookupF[level, signIndex];

                for (int j = 0; j < M; j++)
                {
                    float uR = uRLookup[j];
                    float uI = uILookup[j];

                    for (int even = j; even < length; even += N)
                    {
                        int odd = even + M;

                        float r = data[odd].Re;
                        float i = data[odd].Im;

                        float odduR = r*uR - i*uI;
                        float odduI = r*uI + i*uR;

                        r = data[even].Re;
                        i = data[even].Im;

                        data[even].Re = r + odduR;
                        data[even].Im = i + odduI;

                        data[odd].Re = r - odduR;
                        data[odd].Im = i - odduI;
                    }
                }
            }
        }

        /// <summary>
        ///   Compute a 1D fast Fourier transform of a dataset of complex numbers.
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "direction"></param>
        public static void FFT(ComplexF[] data, FourierDirection direction)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            FFT(data, data.Length, direction);
        }

        /// <summary>
        ///   Compute a 1D fast Fourier transform of a dataset of complex numbers.
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "length"></param>
        /// <param name = "direction"></param>
        public static void FFT(Complex[] data, int length, FourierDirection direction)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            if (data.Length < length)
            {
                throw new ArgumentOutOfRangeException("length", length, "must be at least as large as 'data.Length' parameter");
            }
            if (IsPowerOf2(length) == false)
            {
                throw new ArgumentOutOfRangeException("length", length, "must be a power of 2");
            }

            SyncLookupTableLength(length);

            int ln = Log2(length);

            // reorder array
            ReorderArray(data);

            // successive doubling
            int N = 1;
            int signIndex = (direction == FourierDirection.Forward) ? 0 : 1;

            for (int level = 1; level <= ln; level++)
            {
                int M = N;
                N <<= 1;

                double[] uRLookup = _uRLookup[level, signIndex];
                double[] uILookup = _uILookup[level, signIndex];

                for (int j = 0; j < M; j++)
                {
                    double uR = uRLookup[j];
                    double uI = uILookup[j];

                    for (int even = j; even < length; even += N)
                    {
                        int odd = even + M;

                        double r = data[odd].Re;
                        double i = data[odd].Im;

                        double odduR = r*uR - i*uI;
                        double odduI = r*uI + i*uR;

                        r = data[even].Re;
                        i = data[even].Im;

                        data[even].Re = r + odduR;
                        data[even].Im = i + odduI;

                        data[odd].Re = r - odduR;
                        data[odd].Im = i - odduI;
                    }
                }
            }
        }

        /// <summary>
        ///   Compute a 1D fast Fourier transform of a dataset of complex numbers.
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "length"></param>
        /// <param name = "direction"></param>
        public static void FFT_Quick(Complex[] data, int length, FourierDirection direction)
        {
            /*if( data == null ) {
                throw new ArgumentNullException( "data" );
            }
            if( data.Length < length ) {
                throw new ArgumentOutOfRangeException( "length", length, "must be at least as large as 'data.Length' parameter" );
            }
            if( Fourier.IsPowerOf2( length ) == false ) {
                throw new ArgumentOutOfRangeException( "length", length, "must be a power of 2" );
            }

            Fourier.SyncLookupTableLength( length );   */

            int ln = Log2(length);

            // reorder array
            ReorderArray(data);

            // successive doubling
            int N = 1;
            int signIndex = (direction == FourierDirection.Forward) ? 0 : 1;

            for (int level = 1; level <= ln; level++)
            {
                int M = N;
                N <<= 1;

                double[] uRLookup = _uRLookup[level, signIndex];
                double[] uILookup = _uILookup[level, signIndex];

                for (int j = 0; j < M; j++)
                {
                    double uR = uRLookup[j];
                    double uI = uILookup[j];

                    for (int even = j; even < length; even += N)
                    {
                        int odd = even + M;

                        double r = data[odd].Re;
                        double i = data[odd].Im;

                        double odduR = r*uR - i*uI;
                        double odduI = r*uI + i*uR;

                        r = data[even].Re;
                        i = data[even].Im;

                        data[even].Re = r + odduR;
                        data[even].Im = i + odduI;

                        data[odd].Re = r - odduR;
                        data[odd].Im = i - odduI;
                    }
                }
            }
        }

        /// <summary>
        ///   Compute a 1D real-symmetric fast fourier transform.
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "direction"></param>
        public static void RFFT(float[] data, FourierDirection direction)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            RFFT(data, data.Length, direction);
        }

        /// <summary>
        ///   Compute a 1D real-symmetric fast fourier transform.
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "length"></param>
        /// <param name = "direction"></param>
        public static void RFFT(float[] data, int length, FourierDirection direction)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            if (data.Length < length)
            {
                throw new ArgumentOutOfRangeException("length", length, "must be at least as large as 'data.Length' parameter");
            }
            if (IsPowerOf2(length) == false)
            {
                throw new ArgumentOutOfRangeException("length", length, "must be a power of 2");
            }

            float c1 = 0.5f, c2;
            float theta = (float) Math.PI/(length/2);

            if (direction == FourierDirection.Forward)
            {
                c2 = -0.5f;
                FFT(data, length/2, direction);
            }
            else
            {
                c2 = 0.5f;
                theta = -theta;
            }

            float wtemp = (float) Math.Sin(0.5*theta);
            float wpr = -2*wtemp*wtemp;
            float wpi = (float) Math.Sin(theta);
            float wr = 1 + wpr;
            float wi = wpi;

            // do / undo packing
            for (int i = 1; i < length/4; i++)
            {
                int a = 2*i;
                int b = length - 2*i;
                float h1r = c1*(data[a] + data[b]);
                float h1i = c1*(data[a + 1] - data[b + 1]);
                float h2r = -c2*(data[a + 1] + data[b + 1]);
                float h2i = c2*(data[a] - data[b]);
                data[a] = h1r + wr*h2r - wi*h2i;
                data[a + 1] = h1i + wr*h2i + wi*h2r;
                data[b] = h1r - wr*h2r + wi*h2i;
                data[b + 1] = -h1i + wr*h2i + wi*h2r;
                wr = (wtemp = wr)*wpr - wi*wpi + wr;
                wi = wi*wpr + wtemp*wpi + wi;
            }

            if (direction == FourierDirection.Forward)
            {
                float hir = data[0];
                data[0] = hir + data[1];
                data[1] = hir - data[1];
            }
            else
            {
                float hir = data[0];
                data[0] = c1*(hir + data[1]);
                data[1] = c1*(hir - data[1]);
                FFT(data, length/2, direction);
            }
        }

        /// <summary>
        ///   Compute a 2D fast fourier transform on a data set of complex numbers (represented as pairs of floats)
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "xLength"></param>
        /// <param name = "yLength"></param>
        /// <param name = "direction"></param>
        public static void FFT2(float[] data, int xLength, int yLength, FourierDirection direction)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            if (data.Length < xLength*yLength*2)
            {
                throw new ArgumentOutOfRangeException("data.Length", data.Length, "must be at least as large as 'xLength * yLength * 2' parameter");
            }
            if (IsPowerOf2(xLength) == false)
            {
                throw new ArgumentOutOfRangeException("xLength", xLength, "must be a power of 2");
            }
            if (IsPowerOf2(yLength) == false)
            {
                throw new ArgumentOutOfRangeException("yLength", yLength, "must be a power of 2");
            }

            int xInc = 1;
            int yInc = xLength;

            if (xLength > 1)
            {
                SyncLookupTableLength(xLength);
                for (int y = 0; y < yLength; y++)
                {
                    int xStart = y*yInc;
                    LinearFFT_Quick(data, xStart, xInc, xLength, direction);
                }
            }

            if (yLength > 1)
            {
                SyncLookupTableLength(yLength);
                for (int x = 0; x < xLength; x++)
                {
                    int yStart = x*xInc;
                    LinearFFT_Quick(data, yStart, yInc, yLength, direction);
                }
            }
        }

        /// <summary>
        ///   Compute a 2D fast fourier transform on a data set of complex numbers
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "xLength"></param>
        /// <param name = "yLength"></param>
        /// <param name = "direction"></param>
        public static void FFT2(ComplexF[] data, int xLength, int yLength, FourierDirection direction)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            if (data.Length < xLength*yLength)
            {
                throw new ArgumentOutOfRangeException("data.Length", data.Length, "must be at least as large as 'xLength * yLength' parameter");
            }
            if (IsPowerOf2(xLength) == false)
            {
                throw new ArgumentOutOfRangeException("xLength", xLength, "must be a power of 2");
            }
            if (IsPowerOf2(yLength) == false)
            {
                throw new ArgumentOutOfRangeException("yLength", yLength, "must be a power of 2");
            }

            int xInc = 1;
            int yInc = xLength;

            if (xLength > 1)
            {
                SyncLookupTableLength(xLength);
                for (int y = 0; y < yLength; y++)
                {
                    int xStart = y*yInc;
                    LinearFFT_Quick(data, xStart, xInc, xLength, direction);
                }
            }

            if (yLength > 1)
            {
                SyncLookupTableLength(yLength);
                for (int x = 0; x < xLength; x++)
                {
                    int yStart = x*xInc;
                    LinearFFT_Quick(data, yStart, yInc, yLength, direction);
                }
            }
        }

        /// <summary>
        ///   Compute a 2D fast fourier transform on a data set of complex numbers
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "xLength"></param>
        /// <param name = "yLength"></param>
        /// <param name = "direction"></param>
        public static void FFT2(Complex[] data, int xLength, int yLength, FourierDirection direction)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            if (data.Length < xLength*yLength)
            {
                throw new ArgumentOutOfRangeException("data.Length", data.Length, "must be at least as large as 'xLength * yLength' parameter");
            }
            if (IsPowerOf2(xLength) == false)
            {
                throw new ArgumentOutOfRangeException("xLength", xLength, "must be a power of 2");
            }
            if (IsPowerOf2(yLength) == false)
            {
                throw new ArgumentOutOfRangeException("yLength", yLength, "must be a power of 2");
            }

            int xInc = 1;
            int yInc = xLength;

            if (xLength > 1)
            {
                SyncLookupTableLength(xLength);
                for (int y = 0; y < yLength; y++)
                {
                    int xStart = y*yInc;
                    LinearFFT_Quick(data, xStart, xInc, xLength, direction);
                }
            }

            if (yLength > 1)
            {
                SyncLookupTableLength(yLength);
                for (int x = 0; x < xLength; x++)
                {
                    int yStart = x*xInc;
                    LinearFFT_Quick(data, yStart, yInc, yLength, direction);
                }
            }
        }

        /// <summary>
        ///   Compute a 3D fast fourier transform on a data set of complex numbers
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "xLength"></param>
        /// <param name = "yLength"></param>
        /// <param name = "zLength"></param>
        /// <param name = "direction"></param>
        public static void FFT3(ComplexF[] data, int xLength, int yLength, int zLength, FourierDirection direction)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            if (data.Length < xLength*yLength*zLength)
            {
                throw new ArgumentOutOfRangeException("data.Length", data.Length, "must be at least as large as 'xLength * yLength * zLength' parameter");
            }
            if (IsPowerOf2(xLength) == false)
            {
                throw new ArgumentOutOfRangeException("xLength", xLength, "must be a power of 2");
            }
            if (IsPowerOf2(yLength) == false)
            {
                throw new ArgumentOutOfRangeException("yLength", yLength, "must be a power of 2");
            }
            if (IsPowerOf2(zLength) == false)
            {
                throw new ArgumentOutOfRangeException("zLength", zLength, "must be a power of 2");
            }

            int xInc = 1;
            int yInc = xLength;
            int zInc = xLength*yLength;

            if (xLength > 1)
            {
                SyncLookupTableLength(xLength);
                for (int z = 0; z < zLength; z++)
                {
                    for (int y = 0; y < yLength; y++)
                    {
                        int xStart = y*yInc + z*zInc;
                        LinearFFT_Quick(data, xStart, xInc, xLength, direction);
                    }
                }
            }

            if (yLength > 1)
            {
                SyncLookupTableLength(yLength);
                for (int z = 0; z < zLength; z++)
                {
                    for (int x = 0; x < xLength; x++)
                    {
                        int yStart = z*zInc + x*xInc;
                        LinearFFT_Quick(data, yStart, yInc, yLength, direction);
                    }
                }
            }

            if (zLength > 1)
            {
                SyncLookupTableLength(zLength);
                for (int y = 0; y < yLength; y++)
                {
                    for (int x = 0; x < xLength; x++)
                    {
                        int zStart = y*yInc + x*xInc;
                        LinearFFT_Quick(data, zStart, zInc, zLength, direction);
                    }
                }
            }
        }

        /// <summary>
        ///   Compute a 3D fast fourier transform on a data set of complex numbers
        /// </summary>
        /// <param name = "data"></param>
        /// <param name = "xLength"></param>
        /// <param name = "yLength"></param>
        /// <param name = "zLength"></param>
        /// <param name = "direction"></param>
        public static void FFT3(Complex[] data, int xLength, int yLength, int zLength, FourierDirection direction)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            if (data.Length < xLength*yLength*zLength)
            {
                throw new ArgumentOutOfRangeException("data.Length", data.Length, "must be at least as large as 'xLength * yLength * zLength' parameter");
            }
            if (IsPowerOf2(xLength) == false)
            {
                throw new ArgumentOutOfRangeException("xLength", xLength, "must be a power of 2");
            }
            if (IsPowerOf2(yLength) == false)
            {
                throw new ArgumentOutOfRangeException("yLength", yLength, "must be a power of 2");
            }
            if (IsPowerOf2(zLength) == false)
            {
                throw new ArgumentOutOfRangeException("zLength", zLength, "must be a power of 2");
            }

            int xInc = 1;
            int yInc = xLength;
            int zInc = xLength*yLength;

            if (xLength > 1)
            {
                SyncLookupTableLength(xLength);
                for (int z = 0; z < zLength; z++)
                {
                    for (int y = 0; y < yLength; y++)
                    {
                        int xStart = y*yInc + z*zInc;
                        LinearFFT_Quick(data, xStart, xInc, xLength, direction);
                    }
                }
            }

            if (yLength > 1)
            {
                SyncLookupTableLength(yLength);
                for (int z = 0; z < zLength; z++)
                {
                    for (int x = 0; x < xLength; x++)
                    {
                        int yStart = z*zInc + x*xInc;
                        LinearFFT_Quick(data, yStart, yInc, yLength, direction);
                    }
                }
            }

            if (zLength > 1)
            {
                SyncLookupTableLength(zLength);
                for (int y = 0; y < yLength; y++)
                {
                    for (int x = 0; x < xLength; x++)
                    {
                        int zStart = y*yInc + x*xInc;
                        LinearFFT_Quick(data, zStart, zInc, zLength, direction);
                    }
                }
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
Moldova (Republic of) Moldova (Republic of)
Interested in computer science, math, research, and everything that relates to innovation. Fan of agnostic programming, don't mind developing under any platform/framework if it explores interesting topics. In search of a better programming paradigm.

Comments and Discussions