Compute Spectrum Test

by aobyrne
Look at Ben Stucki's "The Math Behind Flash’s FFT Results" at http://blog.benstucki.net/?p=60
♥0 | Line 263 | Modified 2012-05-12 23:39:08 | MIT License
play

ActionScript3 source code

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

package  
{
    import flash.display.Sprite;
    
    /**
     * ...
     * @author sandcastls
     */
    public class ComputeSpactrumTest extends Sprite 
    {
        
        public function ComputeSpactrumTest() 
        {
            addChild(new AbstractFrequencyAnalizer)
        }
        
    }

}
import com.bit101.components.CheckBox;
import com.bit101.components.HBox;
import com.bit101.components.HSlider;
import com.bit101.components.HUISlider;
import com.bit101.components.Label;
import com.bit101.components.TextArea;
import com.bit101.components.VBox;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.SampleDataEvent;
import flash.events.TimerEvent;
import flash.media.Sound;
import flash.media.SoundMixer;
import flash.utils.ByteArray;
import flash.utils.Timer;



class AbstractFrequencyAnalizer extends Sprite
{
    public var frequency:Number = 0;
    public var minFrequency:Number = 0;
    public var maxFrequency:Number = 11025; // index/samples * sampleRate (256/1024 * 44100)
    protected var logarithmic:Boolean = false;
    protected var notes:Array=[];
    protected var f:Number = 6.875;
    private var sliderBar:HSlider;
    private var frequencyField:Label;
    protected var display:Shape;
    private var sampleRatingInverse:Number;
    private var marginx:Number;
    private var labelNonNullIndexes:Label;
    private var octaveFreqs:Array;
    private var octavesAmount:Number;
    private var chromaticScalesAmount:Number;
    private var allOctaves:Array;
    private var currentFrequency:Number;
    private var count:uint;
    private var octaveIndex:int=-1;
    private var isNonIndexPrinting:Boolean;
    private var textArea:TextArea;
    private var visWidth:Number;
    private var visHeight:Number;
    private var visY:Number;
    public function AbstractFrequencyAnalizer()
    {
        if (stage) 
        {
            init(null)
        }
        else
        {
            addEventListener(Event.ADDED_TO_STAGE, init);
        }
    }
    
    private function init(e:Event):void 
    {
        marginx = 5;
        visWidth = 465 - marginx * 2;
        visHeight = 465 - marginx * 2;
        visY = 250;
        
        
        removeEventListener(Event.ADDED_TO_STAGE, init);
        // setup
        
        display = new Shape();
        addChild(display);
        var vBox:VBox = new VBox(this, 10, 10);
        frequencyField = new Label(vBox, 0, 0, "frequencyField");
        sliderBar = new HSlider(this, marginx, visY+10, mouseMoveHandler);
        sliderBar.visible = false;
        sliderBar.setSize(visWidth, sliderBar.height);
        var sound:Sound = new Sound();
        sound.addEventListener(SampleDataEvent.SAMPLE_DATA, sampleDataHandler);
        sampleRatingInverse = 1 / 44100;
        sound.play();
        sliderBar.value = translate(frequency, minFrequency, maxFrequency, sliderBar.minimum, sliderBar.maximum);
        frequencyField.text = frequency.toString();
        textArea = new TextArea(this, marginx, sliderBar.y+20 );
        
        textArea.setSize(visWidth, 465-textArea.y-10);
        setFrequencyLabels();
        updateNotes();
        labelNonNullIndexes = new Label(vBox, 0, 0);
        chromaticScalesAmount = 12;
        octavesAmount = 11;
        var p:Number = Math.pow(2, 1 / chromaticScalesAmount);
        var thfr:Number = 1;
        thfr = 440;
        var semitonesForA:Number = 10;
        for (var k:int = 0; k < semitonesForA; k++) 
        {
            thfr /= p;
        }
        trace( "thfr : " + thfr );
        var thFreqForCentralC:Number = thfr;
        var octavesUnderCentral:Number = 5;
        for (var l:int = 0; l < octavesUnderCentral; l++) 
        {
            thFreqForCentralC *= 0.5;
        }
        trace( "thFreqForCentralC : " + thFreqForCentralC );
        thfr = thFreqForCentralC;
        allOctaves = [];
        for (var i:int = 0; i < octavesAmount; i++) 
        {
            allOctaves[i] = [];
            octaveFreqs = allOctaves[i];
            for (var j:int = 0; j < chromaticScalesAmount; j++) 
            {
                thfr *= p;
                octaveFreqs[j] = thfr;
                trace( "thfr : " + thfr );
                
            }
            trace('--------------------');
        }
        var aol:uint = allOctaves.length;
        while (aol--) 
        {
            var array:Array = allOctaves[aol] as Array;
            trace( "array : " + array.length );
        }
        //new CheckBox(vBox, 0, 0, 'logarithmic', onLogarithmic);
        var timer:Timer = new Timer(100, octavesAmount * chromaticScalesAmount);
        timer.addEventListener(TimerEvent.TIMER, onRunTest);
        timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTestComplete);
        timer.start();
        frequency = allOctaves[0][0];
        updateStats();
        addEventListener(Event.ENTER_FRAME, loop);

    }
    
    private function onTestComplete(e:TimerEvent):void 
    {
        //isNonIndexPrinting = 
        sliderBar.visible = true;
    }
    
    private function onRunTest(e:TimerEvent):void 
    {
        var timer:Timer = e.target as Timer;
        var noteIndex:Number = count % chromaticScalesAmount;
        if (noteIndex == 0) octaveIndex++;
        trace( "pair : " +octaveIndex+','+ noteIndex );
        frequency = allOctaves[octaveIndex][noteIndex] as Number;
        updateStats();
        trace( "frequency : " + frequency.toFixed(4) );
        //if (noteIndex == chromaticScalesAmount - 1)
        count++;
        isNonIndexPrinting = true;
    }
    
    private function onLogarithmic(e:Event):void 
    {
        logarithmic = CheckBox(e.target).selected;
    }
    
    
    protected function setFrequencyLabels():void 
    {
        isNonIndexPrinting = false;
    }
    private function sampleDataHandler(event:SampleDataEvent):void {
        var sample:Number;
        var pre:Number = 2 * Math.PI * sampleRatingInverse * frequency;
        for (var i:int = 0; i < 4096; i++) 
        {
            sample = Math.sin((i+event.position) * pre)*0.1;
            event.data.writeFloat(sample);
            event.data.writeFloat(sample);
        }
    }        
    private function mouseMoveHandler(event:Event):void {
//            var sliderWidth:Number = sliderBar.width - sliderHandle.width;
//            sliderHandle.x = Math.max(sliderBar.x, Math.min(sliderBar.x + sliderWidth, root.mouseX));
        updateFrequency();
        updateStats();
    }
    
    protected function updateFrequency():void {
//            var sliderWidth:Number = sliderBar.width - sliderHandle.width;
        if(logarithmic) {
            frequency = translateLinearToLog(sliderBar.value, sliderBar.minimum, sliderBar.maximum, minFrequency, maxFrequency);
        } else {
            frequency = translate(sliderBar.value, sliderBar.minimum, sliderBar.maximum, minFrequency, maxFrequency);
        }
    }
    
    //private function trackNative(myRythm:SimpleRhythm):void 
    private function loop(e:Event):void 
    {
        var rf:Number;
        var amplitude:Number;
        var bytes:ByteArray = new ByteArray();
        SoundMixer.computeSpectrum(bytes, true, 0);
        display.graphics.clear();
        display.graphics.lineStyle(0, 0x999999);
        display.graphics.moveTo(marginx, 200);
        //pixels per unit
        var ppuX:Number = visWidth / 256;
        var ppuY:Number = 160;
        
        var nonNullIndexes:Array = [];
        var array:Array = [];
        for (var i:uint = 0; i < 256; ++i) 
        {
            rf = bytes.readFloat();
            if (rf > 0)
            {
                nonNullIndexes[nonNullIndexes.length] = i;
                array[array.length] = rf;
            }
            amplitude = visY - (rf * ppuY);
            display.graphics.lineTo( marginx + (i*ppuX), amplitude);
        }
        var aMax:Number = arrayMax(array);
        if (isNonIndexPrinting) 
        {
            isNonIndexPrinting = false;
            labelNonNullIndexes.text = String(nonNullIndexes )
            textArea.textField.appendText(int(frequency).toString()+':'+aMax.toFixed(4)+': '+nonNullIndexes + '\n');
            textArea.textField.scrollV = textArea.textField.numLines;
        }
        
    }
    private function onSlide(e:Event):void
    {
            
    }
    public function updateNotes():void {
        trace( "AbstractFrequencyAnalizer.updateNotes" );
        trace( "sliderBar.x : " + sliderBar.x );
        trace( "sliderBar.x + sliderBar.width : " + (sliderBar.x + sliderBar.width) );
        trace( "sliderBar.width : " + sliderBar.width );
        for( var i:String in notes ) trace( "key : " + i + ", value : " + notes[ i ] );
        for each(var note:Object in notes) {
            trace( "note : " + note );
            if(logarithmic) {
                note.field.x = translateLogToLinear(note.frequency, minFrequency, maxFrequency, sliderBar.x, sliderBar.x+sliderBar.width);
            } else {
                
                note.field.x = translate(note.frequency, minFrequency, maxFrequency, sliderBar.x, sliderBar.x + sliderBar.width);
            }
            note.field.x += 8; // offset for rotation
            note.field.y = sliderBar.y + 7;
            note.field.rotation = 45/(Math.PI/180);
        }
    }
    private function arrayMax(input:Array):Number 
    {
        var i:Array = input;
        var il:uint = i.length;
        var output:Number = 0;
        for (var j:int = 0; j < il; j++) 
        {
            output=Math.max(output,i[j])
        }
        return output;
    }
    private function updateStats():void {
        //var percent:Number = (sliderHandle.x - sliderBar.x)/(sliderBar.width - sliderHandle.width);
        frequencyField.text = (Math.round(frequency)).toString() + " Hz";
        //percentField.text = (Math.round(percent*1000)/10).toString() + "%";
    }        // utility
    
    public function translate(value:Number, minimum:Number, maximum:Number, minRange:Number, maxRange:Number):Number {
        var percent:Number = (value-minimum)/(maximum-minimum);
        return (maxRange - minRange)*percent + minRange;
    }
    
    public function translateLinearToLog(value:Number, minimum:Number, maximum:Number, minLog:Number, maxLog:Number):Number {
        var peak:Number = Math.log(maxLog-minLog)/Math.log(2);
        var percent:Number = (value-minimum)/(maximum-minimum);
        return Math.pow(2, percent*peak) + minLog;
    }
    
    public function translateLogToLinear(value:Number, minLog:Number, maxLog:Number, minimum:Number, maximum:Number):Number {
        var percent:Number = Math.log(value-minLog)/Math.log(maxLog-minLog);
        return (maximum-minimum)*percent + minimum;
    }
    
}

Forked