forked from: forked from: FFT2

by windy
♥0 | Line 310 | Modified 2011-05-03 00:43:57 | MIT License
play

ActionScript3 source code

/**
 * Copyright windy ( http://wonderfl.net/user/windy )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/8VDx
 */

// forked from Agisce's forked from: FFT2
package
{
    import __AS3__.vec.Vector;
    import flash.display.Sprite;
    import flash.events.*;
    import flash.media.Microphone;
    import flash.text.*;
    import flash.utils.*;

    /**
     * A real-time spectrum analyzer.
     *
     * Released under the MIT License
     *
     * Copyright (c) 2010 Gerald T. Beauregard
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy
     * of this software and associated documentation files (the "Software"), to
     * deal in the Software without restriction, including without limitation the
     * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
     * sell copies of the Software, and to permit persons to whom the Software is
     * furnished to do so, subject to the following conditions:
     *
     * The above copyright notice and this permission notice shall be included in
     * all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
     * IN THE SOFTWARE.
     */

    [SWF(width='600', height='400', frameRate='30', backgroundColor='0xFFFFFF')]
    public class SpectrumAnalyzer extends Sprite
    {
        private const SAMPLE_RATE:Number = 22050;    // Actual microphone sample rate (Hz)
        private const LOGN:uint = 11;                // Log2 FFT length
        private const N:uint = 1 << LOGN;            // FFT Length
        private const BUF_LEN:uint = N;                // Length of buffer for mic audio
        private const UPDATE_PERIOD:int = 50;        // Period of spectrum updates (ms)

        private var m_fft:FFT;                        // FFT object

        private var m_tempRe:Vector.<Number>;        // Temporary buffer - real part
        private var m_tempIm:Vector.<Number>;        // Temporary buffer - imaginary part
        private var m_mag:Vector.<Number>;            // Magnitudes (at each of the frequencies below)
        private var m_freq:Vector.<Number>;            // Frequencies (for each of the magnitudes above)
        private var m_win:Vector.<Number>;            // Analysis window (Hanning)

        private var m_mic:Microphone;                // Microphone object
        private var m_writePos:uint = 0;            // Position to write new audio from mic
        private var m_buf:Vector.<Number> = null;    // Buffer for mic audio

        private var m_tickTextAdded:Boolean = false; 

        private var m_timer:Timer;                    // Timer for updating spectrum

        /**
         *
         */
        public function SpectrumAnalyzer()
        {
            var i:uint;

            // Set up the FFT
            m_fft = new FFT();
            m_fft.init(LOGN);
            m_tempRe = new Vector.<Number>(N);
            m_tempIm = new Vector.<Number>(N);
            m_mag = new Vector.<Number>(N/2);
            //m_smoothMag = new Vector.<Number>(N/2);

            // Vector with frequencies for each bin number. Used
            // in the graphing code (not in the analysis itself).
            m_freq = new Vector.<Number>(N/2);
            for ( i = 0; i < N/2; i++ )
                m_freq[i] = i*SAMPLE_RATE/N;

            // Hanning analysis window
            m_win = new Vector.<Number>(N);
            for ( i = 0; i < N; i++ )
                m_win[i] = (4.0/N) * 0.5*(1-Math.cos(2*Math.PI*i/N));

            // Create a buffer for the input audio
            m_buf = new Vector.<Number>(BUF_LEN);
            for ( i = 0; i < BUF_LEN; i++ )
                m_buf[i] = 0.0;

            // Set up microphone input
            m_mic = Microphone.getMicrophone();
            m_mic.setLoopBack(true);
            m_mic.rate = SAMPLE_RATE/1000;
            m_mic.setSilenceLevel(0.0);            // Have the mic run non-stop, regardless of the input level
            m_mic.addEventListener( SampleDataEvent.SAMPLE_DATA, onMicSampleData );

            // Set up a timer to do periodic updates of the spectrum
            m_timer = new Timer(UPDATE_PERIOD);
            m_timer.addEventListener(TimerEvent.TIMER, updateSpectrum);
            m_timer.start();
        }

        /**
         * Called whether new microphone input data is available. See this call
         * above:
         *    m_mic.addEventListener( SampleDataEvent.SAMPLE_DATA, onMicSampleData );
         */
        private function onMicSampleData( event:SampleDataEvent ):void
        {
            // Get number of available input samples
            var len:uint = event.data.length/4;

            // Read the input data and stuff it into
            // the circular buffer
            for ( var i:uint = 0; i < len; i++ )
            {
                m_buf[m_writePos] = event.data.readFloat();
                m_writePos = (m_writePos+1)%BUF_LEN;
            }
        }

        /**
         * Called at regular intervals to update the spectrum
         */
        private function updateSpectrum( event:Event ):void
        {
            // Copy data from circular microphone audio
            // buffer into temporary buffer for FFT, while
            // applying Hanning window.
            var i:int;
            var pos:uint = m_writePos;
            for ( i = 0; i < N; i++ )
            {
                m_tempRe[i] = m_win[i]*m_buf[pos];
                pos = (pos+1)%BUF_LEN;
            }

            // Zero out the imaginary component
            for ( i = 0; i < N; i++ )
                m_tempIm[i] = 0.0;

            // Do FFT and get magnitude spectrum
            m_fft.run( m_tempRe, m_tempIm );
            for ( i = 0; i < N/2; i++ )
            {
                var re:Number = m_tempRe[i];
                var im:Number = m_tempIm[i];
                m_mag[i] = Math.sqrt(re*re + im*im);
            }

            // Convert to dB magnitude
            const SCALE:Number = 20/Math.LN10;
            for ( i = 0; i < N/2; i++ )
            {
                // 20 log10(mag) => 20/ln(10) ln(mag)
                // Addition of MIN_VALUE prevents log from returning minus infinity if mag is zero
                m_mag[i] = SCALE*Math.log( m_mag[i] + Number.MIN_VALUE );
            }

            // Draw the graph
            drawSpectrum( m_mag, m_freq );
        }

        /**
         * Draw a graph of the spectrum
         */
        private function drawSpectrum(
            mag:Vector.<Number>,
            freq:Vector.<Number> ):void
        {
            // Basic constants
            const MIN_FREQ:Number = 0;                    // Minimum frequency (Hz) on horizontal axis.
            const MAX_FREQ:Number = 4000;                // Maximum frequency (Hz) on horizontal axis.
            const FREQ_STEP:Number = 500;                // Interval between ticks (Hz) on horizontal axis.
            const MAX_DB:Number = -0.0;                    // Maximum dB magnitude on vertical axis.
            const MIN_DB:Number = -60.0;                // Minimum dB magnitude on vertical axis.
            const DB_STEP:Number = 10;                    // Interval between ticks (dB) on vertical axis.
            const TOP:Number  = 50;                        // Top of graph
            const LEFT:Number = 60;                        // Left edge of graph
            const HEIGHT:Number = 300;                    // Height of graph
            const WIDTH:Number = 500;                    // Width of graph
            const TICK_LEN:Number = 10;                    // Length of tick in pixels
            const LABEL_X:String = "Frequency (Hz)";    // Label for X axis
            const LABEL_Y:String = "dB";                // Label for Y axis

            // Derived constants
            const BOTTOM:Number = TOP+HEIGHT;                    // Bottom of graph
            const DBTOPIXEL:Number = HEIGHT/(MAX_DB-MIN_DB);    // Pixels/tick
            const FREQTOPIXEL:Number = WIDTH/(MAX_FREQ-MIN_FREQ);// Pixels/Hz 

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

            var i:uint;
            var numPoints:uint;

            numPoints = mag.length;
            if ( mag.length != freq.length )
                trace( "mag.length != freq.length" );

            graphics.clear();

            // Draw a rectangular box marking the boundaries of the graph
            graphics.lineStyle( 1, 0x000000 );
            graphics.drawRect( LEFT, TOP, WIDTH, HEIGHT );
            graphics.moveTo(LEFT, TOP+HEIGHT);

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

            // Tick marks on the vertical axis
            var y:Number;
            var x:Number;
            for ( var dBTick:Number = MIN_DB; dBTick <= MAX_DB; dBTick += DB_STEP )
            {
                y = BOTTOM - DBTOPIXEL*(dBTick-MIN_DB);
                graphics.moveTo( LEFT-TICK_LEN/2, y );
                graphics.lineTo( LEFT+TICK_LEN/2, y );
                if ( m_tickTextAdded == false )
                {
                    // Numbers on the tick marks
                    var t:TextField = new TextField();
                    t.text = int(dBTick).toString();
                    t.width = 0;
                    t.height = 20;
                    t.x = LEFT-20;
                    t.y = y - t.textHeight/2;
                    t.autoSize = TextFieldAutoSize.CENTER;
                    addChild(t);
                }
            } 

            // Label for vertical axis
            if ( m_tickTextAdded == false )
            {
                t = new TextField();
                t.text = LABEL_Y;
                t.x = LEFT-50;
                t.y = TOP + HEIGHT/2 - t.textHeight/2;
                t.height = 20;
                t.width = 50;
                //t.rotation = -90;
                addChild(t);
            }

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

            // Tick marks on the horizontal axis
            for ( var f:Number = MIN_FREQ; f <= MAX_FREQ; f += FREQ_STEP )
            {
                x = LEFT + FREQTOPIXEL*(f-MIN_FREQ);
                graphics.moveTo( x, BOTTOM - TICK_LEN/2 );
                graphics.lineTo( x, BOTTOM + TICK_LEN/2 );
                if ( m_tickTextAdded == false )
                {
                    t = new TextField();
                    t.text = int(f).toString();
                    t.width = 0;
                    t.x = x;
                    t.y = BOTTOM+7;
                    t.autoSize = TextFieldAutoSize.CENTER;
                    addChild(t);
                }
            }

            // Label for horizontal axis
            if ( m_tickTextAdded == false )
            {
                t = new TextField();
                t.text = LABEL_X;
                t.width = 0;
                t.x = LEFT+WIDTH/2;
                t.y = BOTTOM+30;
                t.autoSize = TextFieldAutoSize.CENTER;
                addChild(t);
            }

            m_tickTextAdded = true;

            // -------------------------------------------------
            // The line in the graph

            // Ignore points that are too far to the left
            for ( i = 0; i < numPoints && freq[i] < MIN_FREQ; i++ )
            {
            }

            // For all remaining points within range of x-axis
            var firstPoint:Boolean = true;
            for ( /**/; i < numPoints && freq[i] <= MAX_FREQ; i++ )
            {
                // Compute horizontal position
                x = LEFT + FREQTOPIXEL*(freq[i]-MIN_FREQ);

                // Compute vertical position of point
                // and clip at top/bottom.
                y = BOTTOM - DBTOPIXEL*(mag[i]-MIN_DB);
                if ( y < TOP )
                    y = TOP;
                else if ( y > BOTTOM )
                    y = BOTTOM;

                // If it's the first point
                if ( firstPoint )
                {
                    // Move to the point
                    graphics.moveTo(x,y);
                    firstPoint = false;
                }
                else
                {
                    // Otherwise, draw line from the previous point
                    graphics.lineTo(x,y);
                }
            }
        }
    }
}


    /**
     * Performs an in-place complex FFT.
     *
     * Released under the MIT License
     *
     * Copyright (c) 2010 Gerald T. Beauregard
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy
     * of this software and associated documentation files (the "Software"), to
     * deal in the Software without restriction, including without limitation the
     * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
     * sell copies of the Software, and to permit persons to whom the Software is
     * furnished to do so, subject to the following conditions:
     *
     * The above copyright notice and this permission notice shall be included in
     * all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
     * IN THE SOFTWARE.
     */
    class FFT
    {
        public static const FORWARD:Boolean = false;
        public static const INVERSE:Boolean = true;

        private var m_logN:uint = 0;            // log2 of FFT size
        private var m_N:uint = 0;                // FFT size
        private var m_invN:Number;                // Inverse of FFT length

        // Bit-reverse lookup table
        private var m_bitRevLen:uint;            // Length of the bit reverse table
        private var m_bitRevFrom:Vector.<uint>;    // "From" indices for swap
        private var m_bitRevTo:Vector.<uint>;    // "To" indices for swap

        /**
         *
         */
        public function FFT()
        {
        }

        /**
         * Initialize class to perform FFT of specified size.
         *
         * @param    logN    Log2 of FFT length. e.g. for 512 pt FFT, logN = 9.
         */
        public function init(
            logN:uint ):void
        {
            m_logN = logN
            m_N = 1 << m_logN;
            m_invN = 1.0/m_N;

            //    Create the bit-reverse table
            m_bitRevFrom = new Vector.<uint>;
            m_bitRevTo   = new Vector.<uint>;
            for ( var k:uint = 0; k < m_N; k++ )
            {
                var kRev:uint = BitReverse(k,logN);
                if (kRev > k)
                {
                    m_bitRevFrom.push(k);
                    m_bitRevTo.push(kRev);
                }
            }
            m_bitRevLen = m_bitRevFrom.length;
        }

        /**
         * Performs in-place complex FFT.
         *
         * @param    xRe        Real part of input/output
         * @param    xIm        Imaginary part of input/output
         * @param    inverse    If true (INVERSE), do an inverse FFT
         */
        public function run(
            xRe:Vector.<Number>,
            xIm:Vector.<Number>,
            inverse:Boolean = false ):void
        {
            var numFlies:uint = m_N >> 1;    // Number of butterflies per sub-FFT
            var span:uint = m_N >> 1;        // Width of the butterfly
            var spacing:uint = m_N;            // Distance between start of sub-FFTs
            var wIndexStep:uint = 1;         // Increment for twiddle table index

            // For each stage of the FFT
            for ( var stage:uint = 0; stage < m_logN; ++stage )
            {
                // Compute a multiplier factor for the "twiddle factors".
                // The twiddle factors are complex unit vectors spaced at
                // regular angular intervals. The angle by which the twiddle
                // factor advances depends on the FFT stage. In many FFT
                // implementations the twiddle factors are cached, but because
                // vector lookup is relatively slow in ActionScript, it's just
                // as fast to compute them on the fly.
                var wAngleInc:Number = wIndexStep * 2.0*Math.PI/m_N;
                if ( inverse )
                    wAngleInc *= -1;
                var wMulRe:Number = Math.cos(wAngleInc);
                var wMulIm:Number = Math.sin(wAngleInc);

                for ( var start:uint = 0; start < m_N; start += spacing )
                {
                    var top:uint    = start;
                    var bottom:uint = top + span;

                    var wRe:Number = 1.0;
                    var wIm:Number = 0.0;

                    // For each butterfly in this stage
                    for ( var flyCount:uint = 0; flyCount < numFlies; ++flyCount )
                    {
                        // Get the top & bottom values
                        var xTopRe:Number = xRe[top];
                        var xTopIm:Number = xIm[top];
                        var xBotRe:Number = xRe[bottom];
                        var xBotIm:Number = xIm[bottom];

                        // Top branch of butterfly has addition
                        xRe[top] = xTopRe + xBotRe;
                        xIm[top] = xTopIm + xBotIm;

                        // Bottom branch of butterly has subtraction,
                        // followed by multiplication by twiddle factor
                        xBotRe = xTopRe - xBotRe;
                        xBotIm = xTopIm - xBotIm;
                        xRe[bottom] = xBotRe*wRe - xBotIm*wIm;
                        xIm[bottom] = xBotRe*wIm + xBotIm*wRe;

                        // Update indices to the top & bottom of the butterfly
                        ++top;
                        ++bottom;

                        // Update the twiddle factor, via complex multiply
                        // by unit vector with the appropriate angle
                        // (wRe + j wIm) = (wRe + j wIm) x (wMulRe + j wMulIm)
                        var tRe:Number = wRe;
                        wRe = wRe*wMulRe - wIm*wMulIm;
                        wIm = tRe*wMulIm + wIm*wMulRe
                    }
                }

                numFlies >>= 1;     // Divide by 2 by right shift
                span >>= 1;
                spacing >>= 1;
                wIndexStep <<= 1;      // Multiply by 2 by left shift
            }

            // The algorithm leaves the result in a scrambled order.
            // Do bit-reversal to unscramble the result.
            for ( var k:uint = 0; k < m_bitRevLen; k++ )
            {
                var brFr:uint = m_bitRevFrom[k];
                var brTo:uint = m_bitRevTo[k];
                var tempRe:Number = xRe[brTo];
                var tempIm:Number = xIm[brTo];
                xRe[brTo] = xRe[brFr];
                xIm[brTo] = xIm[brFr];
                xRe[brFr] = tempRe;
                xIm[brFr] = tempIm;
            }

            //    Divide everything by n for inverse
            if ( inverse )
            {
                for ( k = 0; k < m_N; k++ )
                {
                    xRe[k] *= m_invN;
                    xIm[k] *= m_invN;
                }
            }
        }

        /**
         * Do bit reversal of specified number of places of an int
         * For example, 1101 bit-reversed is 1011
         *
         * @param    x        Number to be bit-reverse.
         * @param    numBits    Number of bits in the number.
         */
        private function BitReverse(
            x:uint,
            numBits:uint):uint
        {
            var y:uint = 0;
            for ( var i:uint = 0; i < numBits; i++)
            {
                y <<= 1;
                y |= x & 0x0001;
                x >>= 1;
            }
            return y;
        }
    }