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

Dynamic Audio Slice Tool & Looper

-- by jeremy brown
-- this is a simple one class example of a slicetool, wave form viewer, looper 
-- drag markers on left and right to crop the sample
-- slider controls level of detail in waveform render
-- more info here: http://labs.makemachine.net/2010/07/slice-tool-looper/
-- slider and button from minimal comps by Keith Peters for
/**
 * Copyright makemachine ( http://wonderfl.net/user/makemachine )
 * GNU General Public License, v3 ( http://www.gnu.org/licenses/quick-guide-gplv3.html )
 * Downloaded from: http://wonderfl.net/c/hEbt
 */

// -- by jeremy brown
// -- this is a simple one class example of a slicetool, wave form viewer, looper 
// -- drag markers on left and right to crop the sample
// -- slider controls level of detail in waveform render
// -- more info here: http://labs.makemachine.net/2010/07/slice-tool-looper/
// -- slider and button from minimal comps by Keith Peters for

package
{
    import com.bit101.components.*;
    
    import flash.display.*;
    import flash.events.*;
    import flash.filters.GlowFilter;
    import flash.media.*;
    import flash.net.URLRequest;
    import flash.utils.ByteArray;

    [SWF( backgroundColor="0x222222", frameRate="60" )]
    public class SliceTool extends Sprite
    {
        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 = 455;
        public static const RENDERER_HEIGHT :int = 150;
        public static const SCRUBBER_HEIGHT :int = 35;
        
        // -- audio
        protected var _insound     :Sound;
        protected var _outsound    :Sound;
        protected var _channel     :SoundChannel;
        protected var _samples     :int;
        protected var _xpos        :Number;
        protected var _lineLength  :Number;
        protected var _position    :int;
        protected var _detail      :int;
        protected var _outsample   :int;
        protected var _playing     :Boolean;
        protected var _playprev    :Boolean;
        protected var _loopflag    :Boolean;
        
        // -- display
        protected var _renderer    :Sprite;
        protected var _scrubber    :Sprite;
        protected var _overlay     :Sprite;
        protected var _init        :Boolean;
        protected var _left        :Sprite;
        protected var _right       :Sprite;
        protected var _grip        :Sprite;
        protected var _progress    :Sprite;
        protected var _playbutton  :PushButton;
        protected var _slider      :HUISlider;
        protected var _label       :Label;
        protected var _progressbar :ProgressBar;
        
      public function SliceTool()
        {
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align     = StageAlign.TOP_LEFT;
            stage.quality   = StageQuality.LOW;
            _label = new Label( this, 200, 100, 'LOADING AUDIO' );
            _progressbar = new ProgressBar( this, 200, 120  );
            _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( "http://labs.makemachine.net/files/flying_lotus_sample.mp3" ) );
            _outsound = new Sound();
        }
        
        // -- update the progress bar
        protected function onLoadProgress( event:ProgressEvent ):void {
            _progressbar.value = event.bytesLoaded / event.bytesTotal;
        }
        
        // -- setup
        protected function onLoadComplete( event:Event ):void
        {
            _detail = 10;
            _outsample =_samples = _insound.length / MILS_PER_SEC * SAMPLE_RATE;
            
            createDisplay();
            updateRenderer();
            
            _insound.removeEventListener( Event.COMPLETE, onLoadComplete );
            _insound.removeEventListener( ProgressEvent.PROGRESS, onLoadProgress );
        }
        
        // ----------------------------------------------
        //
        //     -- renderer
        //
        // ----------------------------------------------
        
        // -- this method draws the wave forms
        // -- if this is the first render it will draw the waveform at the bottom of the display
        // -- we break the render up into chunks so as to achieve smoother redraw
        // -- each frame we take 8192 samples from the loaded sound
        // -- and draw evey other nth sample based on the level of detail
        // -- detail of 10 means every 10th sample is drawn, a detail of 500 means every 500th sample is drawn
        protected function onRenderWaveform( even:Event ):void
        {
            // -- initialize the graphics
            var r:Graphics = _renderer.graphics;
            var s:Graphics = _scrubber.graphics;
            
            if( _xpos == 0 )
            {
                r.moveTo( PADDING, PADDING + RENDERER_HEIGHT * .5 );
                r.lineStyle( 1, 0xf05151, 1, true, LineScaleMode.NONE );
                
                if( !_init ) {
                    s.moveTo( PADDING, PADDING * 2 + RENDERER_HEIGHT + ( SCRUBBER_HEIGHT * .5 ) )
                    s.lineStyle( 1, 0xFFFFFF, 1, true, LineScaleMode.NONE );
                }
            }
            
            // -- draw waveforms
            var n:Number;
            var bytes:ByteArray = new ByteArray();
            var length:int = _position + BUFFER_SIZE < _outsample ? BUFFER_SIZE  : _outsample - _position;
            
            _insound.extract( bytes, length, _position );
            bytes.position = 0;
            
            while( bytes.position < bytes.length )
            {
                // -- average left and right channles
                n = bytes.readFloat() + bytes.readFloat();
                n *= .5;
                
                // -- this modulus allows us to only draw every other nth sample
                if( _position % _detail == 0 )
                {
                    r.lineTo( PADDING + _xpos, 
                            ( PADDING * 2 + RENDERER_HEIGHT * .5 ) + n * RENDERER_HEIGHT * .5 );
                    
                    
                    if( !_init ) {
                        s.lineTo( PADDING + _xpos, 
                                  ( ( PADDING * 2 ) + RENDERER_HEIGHT + 
                                  ( SCRUBBER_HEIGHT * .5 ) ) + n * SCRUBBER_HEIGHT * .5 );
                    }
                    
                    _xpos += _lineLength;
                }
                
                // -- increment the position
                _position ++;
            }
            
            if( _position == _outsample ) {
                onRenderWaveformComplete();
                return;    
            }
        }
        
        // -- initializes variables before render, disable the view
        protected function updateRenderer():void
        {
            _xpos = 0;
            _renderer.graphics.clear();
            _position = map( _left.x, PADDING, WIDTH + PADDING, 0, _samples );
            _outsample = map( _right.x, PADDING, WIDTH + PADDING, 0, _samples );
            _lineLength = WIDTH / ( (_outsample - _position ) / _detail );
            
            mouseChildren = false;
            addEventListener( Event.ENTER_FRAME, onRenderWaveform );
        }
        
        // -- once the render is done check to see if playing 
        // -- before render started and reset position and ousample and start playback
        protected function onRenderWaveformComplete():void
        {
            _init = true;
            
            _position = map( _left.x, PADDING, WIDTH + PADDING, 0, _samples );
            _outsample = map( _right.x, PADDING, WIDTH + PADDING, 0, _samples );
            
            if( _playprev ) {
                play();
                _playprev = false;
            }
            
            removeEventListener( Event.ENTER_FRAME, onRenderWaveform );
            
            mouseChildren = true;
        }
        
        // ----------------------------------------------
        //
        //     -- sound
        //
        // ----------------------------------------------
        // -- toggles between play and stop
        protected function toggle( event:Event = null ):void
        {
            if( _playing ) {
                stop();
                _playprev = false;
            }else {
                play();
                _playprev = true;
            }
        }
        
        protected function stop():void
        {
            if( _channel && _playing ) 
            {
                _progress.scaleX = 0;
                _playing = _loopflag = false;
                _playbutton.label = 'Play';
                _playbutton.selected = false;
                removeEventListener( Event.ENTER_FRAME, onPlayProgress );
                
                _channel.stop();
                _channel.removeEventListener( Event.SOUND_COMPLETE, onSoundComplete );
                _channel = null;
            }
        }
        
        protected function play():void
        {
            _loopflag = false;
            _playing = _playprev = true;
            _playbutton.label = 'Stop';
            _playbutton.selected = true;
            _outsound.addEventListener( SampleDataEvent.SAMPLE_DATA, onSampleData );
            _channel = _outsound.play();
            _channel.addEventListener( Event.SOUND_COMPLETE, onSoundComplete );
            addEventListener( Event.ENTER_FRAME, onPlayProgress );
        }
        
        // -- keep filling the buffer with data
        // -- use _position to indicate where the audio 
        protected function onSampleData( event:SampleDataEvent ):void
        {
            var bytes:ByteArray = new ByteArray();
            var length:int = BUFFER_SIZE;
            
            if( _loopflag ) return;
            
            if( _position + event.position + BUFFER_SIZE > _outsample ) {
                length =  _outsample - ( _position + event.position );
                _loopflag = true;
            }
            
            _insound.extract( bytes, length, _position + event.position );
            
            event.data.writeBytes( bytes );
        }
        
        // -- starts audio as soon as it's ended
        protected function onSoundComplete( event:Event ):void {
            play();
        }
        
        protected function onPlayProgress( event:Event ):void
        {
            var position:int = ( _channel.position / MILS_PER_SEC * SAMPLE_RATE );
            var total:int = _outsample - _position;
            
            _progress.scaleX = position / total;
        }
        
        // ----------------------------------------------
        //
        //     -- grip mouse / move handlers
        //
        // ----------------------------------------------
        protected function onGripMove( event:Event ):void
        {
            // -- constrain the grip positions
            if( _grip == _left ) {
                _grip.x = Math.max( PADDING, stage.mouseX );
                _grip.x = Math.min( _grip.x, _right.x - 10 );
            } 
            
            if( _grip == _right ) {
                _grip.x = Math.max( _left.x + 10, stage.mouseX );
                _grip.x = Math.min( _grip.x, PADDING + WIDTH);
            }
            
            // -- redraw and position the overlay
            drawRect( _overlay, 0, 0, Math.abs( _right.x - _left.x ), SCRUBBER_HEIGHT, 0x34d0d9, 1 );
            _overlay.x = _left.x;
        }
        
        protected function onGripMouseOver( event:MouseEvent ):void
        {
            if( event.target is Sprite ) 
            {
                var sprite:Sprite = event.target as Sprite;
                drawGrip( sprite, 0x00FF00 );
            }
        }
        
        protected function onGripMouseOut( event:MouseEvent ):void
        {
            if( event.target is Sprite ) 
            {
                var sprite:Sprite = event.target as Sprite;
                drawGrip( sprite, 0xf05151 );
            }
        }
        
        protected function onGripMouseDown( event:MouseEvent ):void 
        {
            if( event.target is Sprite ) 
            {
                stop();
                
                _grip = event.target as Sprite;
                drawGrip( _grip, 0xFFCC00 );
                
                stage.addEventListener( MouseEvent.MOUSE_UP, onGripMoveComplete );
                addEventListener( Event.ENTER_FRAME, onGripMove );
                _grip.removeEventListener( MouseEvent.MOUSE_OUT, onGripMouseOut );
                _grip.removeEventListener( MouseEvent.MOUSE_OVER, onGripMouseOver );
            }
        }
        
        protected function onGripMoveComplete( event:MouseEvent ):void
        {    
            removeEventListener( Event.ENTER_FRAME, onGripMove );
            stage.removeEventListener( MouseEvent.MOUSE_UP, onGripMoveComplete );
            _grip.addEventListener( MouseEvent.MOUSE_OUT, onGripMouseOut );
            _grip.addEventListener( MouseEvent.MOUSE_OVER, onGripMouseOver );
            drawGrip( _grip, 0xf05151 );
            _grip = null;
            
            updateRenderer();
        }
        
        // ----------------------------------------------
        //
        //     -- scrubber mouse / move handlers
        //
        // ----------------------------------------------
        
        protected function onScrubberMouseDown( event:Event ):void
        {
            stop();
            addEventListener( Event.ENTER_FRAME, onScrubberMove );
            stage.addEventListener( MouseEvent.MOUSE_UP, onScrubberMoveComplete );
        }
        
        protected function onScrubberMove( event:Event ):void
        {
            var halfw:Number = _overlay.width * .5;
            _overlay.x = Math.max( PADDING, stage.mouseX - halfw );
            _overlay.x = Math.min( PADDING + WIDTH - _overlay.width, _overlay.x );
            _left.x = _overlay.x;
            _right.x = _overlay.x + _overlay.width;
        }
        
        protected function onScrubberMoveComplete( event:Event ):void
        {
            updateRenderer();
            stage.removeEventListener( MouseEvent.MOUSE_UP, onScrubberMoveComplete );
            removeEventListener( Event.ENTER_FRAME, onScrubberMove );
        }
        
        // ----------------------------------------------
        //
        //     -- slider
        //
        // ----------------------------------------------
        
        protected function onSlider( event:Event ):void
        {
            stop();
            _detail = _slider.value;
            updateRenderer();
        }
        
        // ----------------------------------------------
        //
        //     -- keyboard
        //
        // ----------------------------------------------
        
        protected function onKeyDown( event:KeyboardEvent ):void
        {
            switch( event.keyCode ) 
            {
                case 32:
                    toggle();
                break;    
            }
        }
        
        // ----------------------------------------------
        //
        //     -- graphics
        //
        // ----------------------------------------------
        
        // -- builds the u.i.
        protected function createDisplay():void
        {
            _label.visible = _progressbar.visible = false;
            
            // -- draw background shapes
            drawRect( this, PADDING, PADDING, WIDTH, RENDERER_HEIGHT, 0xFFFFFF, .15, false );
            drawRect( this, PADDING, PADDING * 2 + RENDERER_HEIGHT, WIDTH, SCRUBBER_HEIGHT, 0xFFFFFF, .15, false );
            
            // -- upper waveform renders selection
            _renderer         = new Sprite();
            _renderer.filters = [ new GlowFilter( 0xf05151, 1, 6, 6, 1 ) ];
            
            // -- lower waveform renders entire wave
            _scrubber         = new Sprite();
            _scrubber.filters = [ new GlowFilter( 0x34d0d9, 1, 3, 3, 1 ) ];
            
            // -- represents the area of the sound being viewed
            _overlay     = new Sprite();
            _overlay.x  = PADDING;
            _overlay.y  = PADDING * 2 + RENDERER_HEIGHT;
            _overlay.blendMode = BlendMode.ADD;
            _overlay.alpha = .5;
            drawRect( _overlay, 0, 0, WIDTH, SCRUBBER_HEIGHT, 0x34d0d9, 1 );
            
            // -- draggable in point
            _left = new Sprite();
            _left.x = PADDING;
            _left.y = _overlay.y;
            drawGrip( _left, 0xf05151 );
            
            // -- draggable out point
            _right = new Sprite();
            _right.x = PADDING + WIDTH;
            _right.y = _overlay.y;
            drawGrip( _right, 0xf05151 );
            
            _progress = new Sprite();
            _progress.x = _progress.y = PADDING;
            _progress.scaleX = 0;
            _progress.blendMode = BlendMode.ADD;
            drawRect( _progress, 0, 0, WIDTH, RENDERER_HEIGHT, 0xFFFFFF, .1 );
            
            _playbutton = new PushButton( this, 360, PADDING * 4 + RENDERER_HEIGHT + SCRUBBER_HEIGHT, 'Play', toggle );
            _playbutton.toggle = true;
            
            // -- controls the level of detail in render
            _slider = new HUISlider( this, 0, 0, 'Waveform Detail', onSlider );
            _slider.minimum = 10;
            _slider.maximum = 500;
            _slider.value = _detail = 10;
            _slider.x = PADDING;
            _slider.y =  PADDING * 5 + RENDERER_HEIGHT + SCRUBBER_HEIGHT;
            _slider.width = 300;
            _slider.labelPrecision = 0;
            
            // -- add to stage
            addChild( _renderer );
            addChild( _scrubber );
            addChild( _overlay );
            addChild( _left );
            addChild( _right );
            addChild( _progress );
            
            // -- add listeners
            stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyDown );
            
            _left.addEventListener( MouseEvent.MOUSE_OVER, onGripMouseOver );
            _left.addEventListener( MouseEvent.MOUSE_DOWN, onGripMouseDown );
            _left.addEventListener( MouseEvent.MOUSE_OUT, onGripMouseOut );
            
            _right.addEventListener( MouseEvent.MOUSE_OVER, onGripMouseOver );
            _right.addEventListener( MouseEvent.MOUSE_DOWN, onGripMouseDown );
            _right.addEventListener( MouseEvent.MOUSE_OUT, onGripMouseOut );
            
            _renderer.addEventListener( MouseEvent.MOUSE_DOWN, toggle );
            
            _overlay.addEventListener( MouseEvent.MOUSE_DOWN, onScrubberMouseDown );
        }
        
        // -- draws boxes in bg
        protected function drawRect( sprite:Sprite, 
                                     xpos:int, ypos:int, 
                                     w:int, h:int, color:uint = 0xFFFFFF, 
                                     a:Number = .25, clear:Boolean = true ):void {
            clear ? 
                sprite.graphics.clear() :
                    null;
            sprite.graphics.beginFill( color, a );
            sprite.graphics.drawRect( xpos, ypos, w, h );
            sprite.graphics.endFill();
        }
        
        protected function drawGrip( grip:Sprite, color:uint ):void
        {
            drawRect( grip, -4, 0, 10, SCRUBBER_HEIGHT, 0xFFCC00, 0 );
            drawRect( grip, grip == _left ? 0 : -4, -5, 5, 5, color, 1, false );
            drawRect( grip, 0, 0, 1, SCRUBBER_HEIGHT, color, 1, false );
            drawRect( grip, grip == _left ? 0 : -4, SCRUBBER_HEIGHT, 5, 5, color, 1, false );
        }
        
        // ----------------------------------------------
        //
        //     -- utils
        //
        // ----------------------------------------------
        
        protected function normalize(value:Number, min:Number, max:Number):Number {
            return (value - min) / (max - min);
        }
        
        protected function interpolate(normValue:Number, min:Number, max:Number):Number {
            return min + (max - min) * normValue;
        }

        protected function map(value:Number, min1:Number, max1:Number, min2:Number, max2:Number):Number {
            return interpolate( normalize(value, min1, max1), min2, max2);
        }
    }
}