/**
* 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;
}
}