In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Digital Delay / Looper / Visualizer

-- Digital Delay Example
-- @author Jeremy Brown
-- more dynamic audio and sound experiments: 
-- http://labs.makemachine.net/
-- This is a simple example of a digital delay / echo effect
-- w/ bmp syncing read more here: 
-- http://labs.makemachine.net/2010/07/echo/
-- u.i. elements are minimal comps by Keith Peters
/**
 * Copyright makemachine ( http://wonderfl.net/user/makemachine )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/trBW
 */

// -- Digital Delay Example
// -- @author Jeremy Brown
// -- more dynamic audio and sound experiments: 
// -- http://labs.makemachine.net/
// -- This is a simple example of a digital delay / echo effect
// -- w/ bmp syncing read more here: 
// -- http://labs.makemachine.net/2010/07/echo/
// -- u.i. elements are minimal comps by Keith Peters

package
{
    import com.bit101.components.*;
    
    import flash.display.*;
    import flash.events.*;
    import flash.media.*;
    import flash.net.*;
    import flash.utils.*;
    
    [SWF( backgroundColor="0x000000", width="620", height="100", frameRate="60" )]
    public class EchoExample extends Sprite
    {
        // -- constants
        public static const BUFFER_SIZE     :int = 8192;
        public static const SAMPLE_RATE     :int = 44100;
        public static const MILS_PER_SEC    :int = 1000;
        public static const PADDING         :int = 5;
        public static const WIDTH           :int = 610;
        public static const DLY_BUFFER_SIZE :int = BUFFER_SIZE * 16;
        public static const TEMPO           :int = 160;
        public static const VIS_HEIGHT      :int = 35;
        
        // -- sound
        protected var _path         :String;
        protected var _insound      :Sound;
        protected var _outsound     :Sound;
        protected var _channel      :SoundChannel;
        protected var _position     :int;
        protected var _playing      :Boolean;
        protected var _samples      :int;
        protected var _bufferindex  :int;
        protected var _delaytime    :int; 
        protected var _delaybuffer  :Vector.<Number>;
        
        // -- display
        protected var _label        :Label;
        protected var _progressbar  :ProgressBar;
        protected var _delayknob    :Knob;
        protected var _feedbackknob :Knob;
        protected var _mixknob      :Knob;
        protected var _playbutton   :PushButton;
        protected var _visualizer   :Sprite;
        
        // -- sync btns
        protected var _syncbtns             :Array;
        protected var _selectedsync         :String;
        public static const SYNC_OPTIONS    :Array = [ 'Off', '1', '2', '4', '8', '16', '32' ];
        
        public function EchoExample()
        {
            _path = 'http://labs.makemachine.net/files/160_bpm_echo_beat.mp3';
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            
            _label = new Label( this, 190, 50, 'LOADING AUDIO' );
            _progressbar = new ProgressBar( this, 190, 70  );
            _progressbar.width = _label.width;
            
            loadAudio();
            
        }
        
        // ----------------------------------------------
        //
        //     -- loading
        //
        // ----------------------------------------------
        
        protected function loadAudio():void
        {
            _insound = new Sound();
            _insound.addEventListener( Event.COMPLETE, onLoadComplete );
            _insound.addEventListener( ProgressEvent.PROGRESS, onLoadProgress );
            _insound.load( new URLRequest( _path ) );
            
            _outsound = new Sound();
        }
        
        protected function onLoadProgress( event:ProgressEvent ):void {
            _progressbar.value = event.bytesLoaded / event.bytesTotal;
        }
        
        // -- create the delay buffer
        // -- a very long vector filled with zeros
        protected function onLoadComplete( event:Event ):void
        {
            createDisplay();
            
            _position = 0;
            _bufferindex = 0;
            _delaytime = 4096;
            
            _delaybuffer = new Vector.<Number>( DLY_BUFFER_SIZE );
            for( var i:int = 0; i < DLY_BUFFER_SIZE; i++ ) {
                _delaybuffer[i] = 0;
            }
            
            // -- hacking this by removing some from the total count
            // -- this corrects poor mp3 looping because of meta-data at the beginning of the file
            _samples = _insound.length / MILS_PER_SEC * SAMPLE_RATE - 3500;
            
            _insound.removeEventListener( Event.COMPLETE, onLoadComplete );
            _insound.removeEventListener( ProgressEvent.PROGRESS, onLoadProgress );
        }
        
        // ----------------------------------------------
        //
        //     -- sound
        //
        // ----------------------------------------------
        
        protected function toggle( event:Event = null ):void
        {
            if( _playing ) {
                stop();
            }else {
                play();
            }
        }
        
        // -- stop audio, stop visualizer, reset u.i.
        protected function stop():void
        {
            if( _channel && _playing ) 
            {
                _playing = false;
                _playbutton.label = 'Play';
                _playbutton.selected = false;
                _channel.stop();
                _channel = null;
                removeEventListener( Event.ENTER_FRAME, onEnterFrame );
            }
        }
        
        // -- reset u.i., start audio, start visualizer
        protected function play():void
        {
            _position = 0;
            _playing  = true;
            _playbutton.label = 'Stop';
            _playbutton.selected = true;
            _outsound.addEventListener( SampleDataEvent.SAMPLE_DATA, onSampleData );
            _channel = _outsound.play();
            addEventListener( Event.ENTER_FRAME, onEnterFrame );
        }
        
        // -- keep filling the buffer with data
        // -- no need for sound complete here, we use the length variable as the increment for position
        // -- creates seamless loop
        protected function onSampleData( event:SampleDataEvent ):void
        {
            var bytes:ByteArray = new ByteArray();
            var length:int = _position + BUFFER_SIZE > _samples ?  _samples - _position : BUFFER_SIZE;
            
            _insound.extract( bytes, length, _position );
            _position = _position + length >= _samples ? 0 : _position + length;
            
            event.data.writeBytes( createDelay( bytes ) );
        }
        
        // -- runs through every sample in the current buffer
        // -- caches each sample in the delay buffer
        // -- reads cached samples n( getDelayTime ) indices away from bufferindex 
        // -- merges them w/ current sample output
        public function createDelay( samples:ByteArray ):ByteArray
        {
            var bytes:ByteArray = new ByteArray();
            
            var output    :Number;
            var write    :Number;
            var history    :Number;
            var index    :Number;
            var delay    :int = getDelayTime();
            var feedback:Number = ( _feedbackknob.value / 10 ) - .1;
            var mix        :Number = _mixknob.value / 10;
            
            samples.position = 0;
            
            while( samples.bytesAvailable )
            {
                // -- current sample
                output = samples.readFloat();
                
                // -- write the current sample into memory
                _delaybuffer[ _bufferindex ] = output;
                
                // -- set the index to from which to get history sample
                index = _bufferindex - delay;
                if( index < 0 ) {
                    index += DLY_BUFFER_SIZE;
                }
                
                // -- merget output w/ history
                history = _delaybuffer[ index ];
                output = history * mix + output * ( 1 - mix ); 
                
                // -- add current history sample & multiply by feedback 
                // -- overtime this value will resolve to zero if nothing else is written to this index
                _delaybuffer[ _bufferindex ] += history * feedback;
                
                bytes.writeFloat( output );
                
                if( ++_bufferindex == DLY_BUFFER_SIZE )
                    _bufferindex = 0;
            }
            
            bytes.position = 0;
            return bytes;
        }
        
        // -- not very pretty but gets the point across
        // -- if there are 44100 samples in a second 
        // -- multiply that by 60 as in 60 seconds per minute
        // -- this gives us the number of samples in a minute
        // -- dividing this number by TEMPO gives us the duration of each beat in samples
        // -- we can then multiply or divide to get whole, half, quater, eighth, & sixteenth notes
        protected function getDelayTime():int 
        {
            var delay:int;
            var noteDuration:Number = SAMPLE_RATE * 60 / TEMPO;
            switch( _selectedsync ) 
            {
                case 'Off':
                    delay = _delayknob.value / MILS_PER_SEC * SAMPLE_RATE;
                    break;
                case '1':
                    delay = noteDuration * 4
                    break;
                
                case '2':
                    delay = noteDuration * 2;
                    break;
                
                case '4':
                    delay = noteDuration;
                    break;
                
                case '8':
                    delay = noteDuration * .5;
                    break;
                
                case '16':
                    delay = noteDuration * .25;
                    break;
                    
                case '32':
                    delay = noteDuration * .125;
                    break;
                default:
                    delay = _delayknob.value / 1000 * SAMPLE_RATE;
                    break;
            }
            
            return delay;
        }
        
        // ----------------------------------------------
        //
        //     -- sync options
        //
        // ----------------------------------------------
        protected function onSyncButtonDown( event:MouseEvent ):void
        {
            if( event.target is PushButton ) 
            {
                var btn:PushButton;
                var targ:PushButton = event.target as PushButton;
                for each( btn in _syncbtns ) 
                {
                    if( btn == targ ) 
                    {
                        btn.selected = true;
                        _selectedsync = btn.label;
                    } else {
                        btn.selected = false;
                    }
                }
                
                _delayknob.mouseEnabled = _delayknob.mouseChildren = _selectedsync == 'Off';
                _delayknob.alpha = _selectedsync == 'Off' ? 1 : .3;
            }
        }
        
        // ----------------------------------------------
        //
        //     -- visualizer
        //
        // ----------------------------------------------
        
        // -- draws a simple visualizer
        protected function onEnterFrame( event:Event ):void
        {
            var bytes:ByteArray = new ByteArray();
            
            if( !SoundMixer.areSoundsInaccessible() ) 
            {
                _visualizer.graphics.clear();
                _visualizer.graphics.lineStyle( 1, 0xf05151, 1, true );
                _visualizer.graphics.drawRect( 0, 0, 390, 35 );
                _visualizer.graphics.moveTo( 0, 32 );
                
                SoundMixer.computeSpectrum( bytes );
                bytes.position = 0;
                
                if( bytes.bytesAvailable ) 
                {
                    var i:int;
                    var n:Number;
                    
                    for( i = 0; i < 256; i++ ) 
                    {
                        n = Math.min( bytes.readFloat(), 1 );
                        n = Math.max( n, -1 );
                        _visualizer.graphics.lineTo( ( 390 / 256 ) * i, (VIS_HEIGHT * .5) + n * ( VIS_HEIGHT * .5 ) );
                    }
                }
            }
        }
        
        
        // ----------------------------------------------
        //
        //     -- graphics
        //
        // ----------------------------------------------
        
        protected function createDisplay():void
        {
            _label.visible = _progressbar.visible = false;
            
            drawRect( this, PADDING, PADDING, 185, 90 );
            drawRect( this, 195, PADDING, 265, 90, 0xFFFFFF, .15, false );
            
            _playbutton = new PushButton( this, 400, 100, 'Play', toggle );
            _playbutton.toggle = true;
            _playbutton.width = 60;
            _playbutton.height = 35;
            
            var hbox:HBox = new HBox( this, PADDING * 4, PADDING * 2 );
            hbox.spacing = 20;
            
            _delayknob = new Knob( hbox, 0, 0, 'Delay (ms)' );
            _delayknob.minimum = 1;
            _delayknob.maximum = 1000;
            _delayknob.value = 20;
            _delayknob.labelPrecision = 0;
            _delayknob.draw();
            
            _feedbackknob = new Knob( hbox, 0, 0, 'Feedback' );
            _feedbackknob.minimum = 0;
            _feedbackknob.maximum = 10;
            _feedbackknob.value = 9;
            _feedbackknob.draw();
            
            _mixknob = new Knob( hbox, 0,  0, 'Mix' );
            _mixknob.minimum = 0;
            _mixknob.maximum = 10;
            _mixknob.value = 5;
            _mixknob.draw();
            
            var btn:PushButton;
            var label:Label = new Label( this, 200, 20, 'BPM Sync' );
            
            hbox = new HBox( this, 205, 40 );
            _syncbtns = [];
            _selectedsync = 'Off';
            var i:int;
            for( i = 0; i < SYNC_OPTIONS.length; i++ ) 
            {
                btn = new PushButton( hbox, 0, 0, SYNC_OPTIONS[i], onSyncButtonDown );
                btn.toggle = true;
                btn.selected = i == 0;
                btn.width = 30;
                _syncbtns.push( btn );
            }
            
            _visualizer = new Sprite();
            _visualizer.x = PADDING;
            _visualizer.y = 100;
            
            _visualizer.graphics.lineStyle( 1, 0xf05151, 1, true );
            _visualizer.graphics.drawRect( 0, 0, 390, VIS_HEIGHT );
            _visualizer.graphics.moveTo( 0, VIS_HEIGHT * .5 );
            _visualizer.graphics.lineTo( 390, VIS_HEIGHT * .5 );
            
            addChild( _visualizer );
        }
        
        // -- draws boxes in bg
        protected function drawRect( sprite:Sprite, 
                                     xpos:int, ypos:int, 
                                     w:int, h:int, color:uint = 0xFFFFFF, 
                                     a:Number = .15, clear:Boolean = true ):void {
            clear ? 
                sprite.graphics.clear() :
                null;
            sprite.graphics.beginFill( color, a );
            sprite.graphics.drawRect( xpos, ypos, w, h );
            sprite.graphics.endFill();
        }
        
    }
}