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

Hey, Mr. DJ

Big thanks to this project: http://blog.glowinteractive.com/2011/01/vinyl-scratch-emulation-on-iphone/
Get Adobe Flash player
by devon_o 23 Jun 2012
/**
 * Copyright devon_o ( http://wonderfl.net/user/devon_o )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/iYlE
 */

package 
{

    import com.bit101.components.Knob;
    import com.bit101.components.Label;
    import com.bit101.components.ProgressBar;
    import com.bit101.components.PushButton;
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.events.ProgressEvent;
    import flash.media.Sound;
    import flash.media.SoundLoaderContext;
    import flash.net.URLRequest;
    import flash.system.Security;

    [SWF(width='465', height='465', backgroundColor='#000000', frameRate='60')]
    public class Main extends Sprite 
    {
        
        private const POLICY:String    = "http://www.onebyonedesign.com/crossdomain.xml";
        private const SOUND:String    = "http://www.onebyonedesign.com/flash/djscratch/scratch.mp3";
        
        private var mLoadedSong:Sound;
        private var mScratchDisplay:ScratchDisplay;
        private var mRightSoundDiplay:ScratchDisplay;
        
        // ui
        private var mToggle:PushButton;
        private var mProgressBar:ProgressBar;
        private var mVolume:Knob;
        
        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(event:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);

            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
            
            Security.loadPolicyFile(POLICY);
            
            initProgressBar();
            loadSound();
        }
        
        private function initProgressBar():void
        {
            mProgressBar = new ProgressBar(this);
            mProgressBar.x = (stage.stageWidth - mProgressBar.width) >> 1;
            mProgressBar.y = (stage.stageHeight - mProgressBar.height) >> 1;
        }
        
        private function loadSound():void
        {
            var s:Sound = new Sound();
            s.addEventListener(Event.COMPLETE, onSoundLoad);
            s.addEventListener(ProgressEvent.PROGRESS, onSoundProgress);
            s.load(new URLRequest(SOUND), new SoundLoaderContext(1000, true));
        }
        
        private function onSoundProgress(event:ProgressEvent):void
        {
            mProgressBar.value = event.bytesLoaded / event.bytesTotal;
        }
        
        private function onSoundLoad(event:Event):void
        {
            event.currentTarget.removeEventListener(Event.COMPLETE, onSoundLoad);
            
            removeChild(mProgressBar);
            
            mScratchDisplay = new ScratchDisplay(event.currentTarget as Sound);
            
            mScratchDisplay.x = (stage.stageWidth - 300) >> 1;
            mScratchDisplay.y = (stage.stageHeight - 150) >> 1;
            addChild(mScratchDisplay);
            
            var l:Label = new Label(this, mScratchDisplay.x, mScratchDisplay.y - 20, "Press 'PLAY'   -   click and drag waveform to scratch");
            
            mToggle = new PushButton(this, mScratchDisplay.x, mScratchDisplay.y + 150 + 50, "PLAY", onToggle);
            mToggle.toggle = true;
            
            mVolume = new Knob(this, mScratchDisplay.x + 300 - 40, mScratchDisplay.y + 150 + 10, "Vol", onVolume);
            mVolume.minimum = 0.0;
            mVolume.maximum = 1.0;
            mVolume.value = 1.0;
        }
        
        private function onToggle(event:MouseEvent):void
        {
            if (mToggle.selected)
            {
                mScratchDisplay.play();
                mToggle.label = "STOP";
            } else {
                mScratchDisplay.stop();
                mToggle.label = "PLAY";
            }
        }
        
        private function onVolume(event:Event):void
        {
            mScratchDisplay.setVolume(mVolume.value);
        }
    }
}




//    ***** SCRATCH DISPLAY ****   //
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.SampleDataEvent;
import flash.geom.Rectangle;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundTransform;
import flash.utils.ByteArray;
import flash.utils.getTimer;

class ScratchDisplay extends Sprite
{
    private const WRITE_SIZE:int             = 2048;
    private const PLAYBACK_FREQUENCY:Number = 44100.0;
    private const DRAG:Number                 = .25;
    private const SPEED_EASE:Number         = 1.0;
    private const SPEED_MULT:Number            = 24;
    private const MOVE_EASE:Number             = 1.0;
        
    private const BYTE_POSITION_TO_PIXELS:Number  = 0.00004;    // original = .0004
        
    public var mSoundData:ByteArray;
        
    private var mSamples:Vector.<Number>;
    private var mNumSamples:Number;
        
    private var mSound:Sound;
    private var mWaveHolder:Sprite;
    private var mPlayingSound:Sound;
    private var mDisplay:Shape;
        
    private var mIsScratching:Boolean;
        
    private var mPosition:Number = 0.0;
    private var mTargetSpeed:Number;
    private var mSpeed:Number = 2.0;
        
    private var mChannel:SoundChannel;
    private var mVolume:SoundTransform;
        
    private var mRatio:Number;
    private var mCenterX:Number;
        
    // throwing
    private var mDiffX:Number;
    private var mVelX:Number = 0.0;
    private var mCurrX:Number;
    private var mPrevX:Number;
    private var mFarLeft:Number;
    private var mFarRight:Number;
        
    public function ScratchDisplay(sound:Sound)
    {
        mSound = sound;
        mPlayingSound = new Sound();
        mVolume = new SoundTransform();
        mWaveHolder = new Sprite();
        drawWaveForm();
        extractSamples();
        initSmoothBuffer();
        initDisplay();
    }
        
    public function play():void
    {
        mPlayingSound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSample);
        mChannel = mPlayingSound.play();
        mWaveHolder.buttonMode = true;
        mWaveHolder.addEventListener(MouseEvent.MOUSE_DOWN, startScratch);
    }
        
    public function stop():void
    {
        mPlayingSound.removeEventListener(SampleDataEvent.SAMPLE_DATA, onSample);
        mChannel.stop();
        mWaveHolder.buttonMode = false;
        mWaveHolder.removeEventListener(MouseEvent.MOUSE_DOWN, startScratch);
    }
        
    public function setVolume(value:Number):void
    {
        mVolume.volume = value;
        mChannel.soundTransform = mVolume;
    }
        
    private function initSmoothBuffer():void
    {
        var i:int = mSmoothBufferSize;
        while (i--)
        {
            mSmoothBuffer[i] = 0.0;
        }
    }
        
    public function wave_interpolator(x:Number, y:Vector.<Number>):Number
    {
        const offset:int = 2;
            
        var z:Number = x - 1 / 2.0; 
        var even1:Number = y[int(offset + 1)] + y[int(offset +  0)], odd1:Number = y[int(offset + 1)] - y[int(offset +  0)];
        var even2:Number = y[int(offset + 2)] + y[int(offset + -1)], odd2:Number = y[int(offset + 2)] - y[int(offset + -1)];
        var even3:Number = y[int(offset + 3)] + y[int(offset + -2)], odd3:Number = y[int(offset + 3)] - y[int(offset + -2)];
                
        var c0:Number = even1 * 0.42685983409379380 + even2 * 0.07238123511170030
            + even3 * 0.00075893079450573;
        var c1:Number = odd1 * 0.35831772348893259 + odd2 * 0.20451644554758297
            + odd3 * 0.00562658797241955;
        var c2:Number = even1 * -0.217009177221292431 + even2 * 0.20051376594086157
            + even3 * 0.01649541128040211;
        var c3:Number = odd1 * -0.25112715343740988 + odd2 * 0.04223025992200458
            + odd3 * 0.02488727472995134;
        var c4:Number = even1 * 0.04166946673533273 + even2 * -0.06250420114356986
            + even3 * 0.02083473440841799;
        var c5:Number = odd1 * 0.08349799235675044 + odd2 * -0.04174912841630993
            + odd3 * 0.00834987866042734;
                
        return ((((c5 * z + c4) * z + c3) * z + c2) * z + c1) * z + c0;
    }
        
    private function onSample(event:SampleDataEvent):void
    {
        var data:ByteArray = event.data;
            
        if (mIsScratching)
        {
            writeScratchStream(data);
                
        } else {
                
            writeNormalStream(data);
            mRatio = mPosition / mNumSamples;
            mWaveHolder.x = mCenterX - (mWaveHolder.width * mRatio);
        }
    }
        
    private function writeNormalStream(stream:ByteArray):void
    {
        for (var i:int = 0; i < WRITE_SIZE; i++)
        {
            var sample:Number = getNextSample();
            
            stream.writeFloat(sample);
            stream.writeFloat(sample);
                
            mPosition += mSpeed;
            if (mPosition >= mNumSamples)     mPosition = 0;
            if (mPosition < 0)                 mPosition = mNumSamples - 1;
        }
    }
        
        
    //    http://blog.glowinteractive.com/2011/01/vinyl-scratch-emulation-on-iphone/
    private var mAudioSampleSize:uint = 8;    // original = sizeof(float) * 2
    private var mScratchingBufferSize:uint = 500000;
    private var mSmoothBufferSize:uint = 3000;
    private var mScratchCircularBufferSamplePosition:int = 0;
    private var mScratchingPositionOffset:Number = 0.0;
    private var mScratchingPositionSmoothedVelocity:Number = 0.0;
    private var mScratchingPositionVelocity:Number = 0.0;
    // for smoothing of input
    private var mSmoothBuffer:Vector.<Number> = new Vector.<Number>(mSmoothBufferSize, true);
    private var mSmoothBufferPositionsUsed:int = 0;
    private var mSmoothBufferPosition:int = 0;
    
    private var mLeftInterpData:Vector.<Number> = new Vector.<Number>(6, true);
    private var mRightInterpData:Vector.<Number> = new Vector.<Number>(6, true);
        
    //    comments from original source. http://blog.glowinteractive.com/2011/01/vinyl-scratch-emulation-on-iphone/
    private function writeScratchStream(stream:ByteArray):void
    {
        var bufferBasePosition:Number = Number(mScratchCircularBufferSamplePosition);
            
        for (var i:int = 0; i < WRITE_SIZE; i++)
        {
            if (++mSmoothBufferPositionsUsed > int(mSmoothBufferSize))
            {
                mSmoothBufferPositionsUsed = mSmoothBufferSize;
            }
                
            // STEP 1
            // find moving average
            mScratchingPositionSmoothedVelocity -= mSmoothBuffer[mSmoothBufferPosition];
            mSmoothBuffer[mSmoothBufferPosition] = mScratchingPositionVelocity / mSmoothBufferPositionsUsed;
            mScratchingPositionSmoothedVelocity += mSmoothBuffer[mSmoothBufferPosition];
            mSmoothBufferPosition = (++mSmoothBufferPosition) % mSmoothBufferSize;
            var velocity:Number = mScratchingPositionSmoothedVelocity;
                
            // STEP 2
            // modify velocity to point to the correct position
            var targetOffset:Number = mPosition + mSpeed;
            var offsetDiff:Number = targetOffset - mPosition;
            velocity += offsetDiff * 10.0;
                
            // STEP 3
            // update scratch buffer position
            mScratchingPositionOffset += velocity / PLAYBACK_FREQUENCY;
                
            // find absolute scratch buffer position
            var fBufferPosition:Number = bufferBasePosition + mScratchingPositionOffset;
                
            // wrap fBufferPosition
            //TODO use fmodf but what's faster?
            while (fBufferPosition >= mScratchingBufferSize) fBufferPosition -= mScratchingBufferSize;
            while (fBufferPosition < 0) fBufferPosition += mScratchingBufferSize;
                
            // STEP 4
            // use interpolation to find a sample value
            var iBufferPosition:int = int(fBufferPosition);
            var rem:Number = fBufferPosition - iBufferPosition;
                
            var pos:int = 2 * (iBufferPosition - 2)
            var sample:Number = readSample(pos);
            mLeftInterpData[0] = sample;
                
            ++pos;
            sample = readSample(pos);
            mRightInterpData[0] = sample;
                
            ++pos;
            sample = readSample(pos);
            mLeftInterpData[1] = sample;
                
            ++pos;
            sample = readSample(pos);
            mRightInterpData[1] = sample;
                
            ++pos;
            sample = readSample(pos);
            mLeftInterpData[2] = sample;
                
            ++pos;
            sample = readSample(pos);
            mRightInterpData[2] = sample;
                
            ++pos;
            sample = readSample(pos);
            mLeftInterpData[3] = sample;
                
            ++pos;
            sample = readSample(pos);
            mRightInterpData[3] = sample;
                
            ++pos;
            sample = readSample(pos);
            mLeftInterpData[4] = sample;
                
            ++pos;
            sample = readSample(pos);
            mRightInterpData[4] = sample;
                
            ++pos;
            sample = readSample(pos);
            mLeftInterpData[5] = sample;
                
            ++pos;
            sample = readSample(pos);
            mRightInterpData[5] = sample;
                
            stream.writeFloat(wave_interpolator(rem, mLeftInterpData));
            stream.writeFloat(wave_interpolator(rem, mRightInterpData));
        }
    }
        
    public function getNextSample():Number
    {
        var intPos:int = int(mPosition);
        return readSample(intPos);
    }
        
    private function readSample(pos:int):Number
    {
        while (pos < 0)             pos += mNumSamples;
        while (pos >= mNumSamples)     pos -= mNumSamples;
            
        return mSamples[pos];
    }
    
    private function clamp(target:Number, min:Number, max:Number):Number 
    {
        if (target < min) target = min;
        if (target > max) target = max;
        return target;
    }
        
    private function startScratch(event:MouseEvent):void
    {
        mIsScratching = true;    
        mPrevX = mWaveHolder.x
        mDiffX = mouseX - mWaveHolder.x;
        stage.addEventListener(MouseEvent.MOUSE_UP, endScratch);
        addEventListener(Event.ENTER_FRAME, mouseMove);
    }
        
    private function endScratch(event:MouseEvent):void
    {
        stage.removeEventListener(MouseEvent.MOUSE_UP, endScratch);
        removeEventListener(Event.ENTER_FRAME, mouseMove);
        addEventListener(Event.ENTER_FRAME, slide);
    }
        
        
    // ENTER FRAME FUNCTION
    private function mouseMove(event:Event):void
    {
        var tx:Number = mouseX - mDiffX;
        mWaveHolder.x += (tx - mWaveHolder.x) / MOVE_EASE;
        if (mWaveHolder.x < mFarLeft) mWaveHolder.x = mFarLeft;
        if (mWaveHolder.x > mFarRight) mWaveHolder.x = mFarRight;
            
        mVelX = mWaveHolder.x - mPrevX;
        mPrevX = mWaveHolder.x;
            
        setSpeed();
            
        setByteOffset(mSpeed / BYTE_POSITION_TO_PIXELS);
        update();
    }
        
    private var mPreviousTime:Number             = 0.0;
    private var mPositionOffset:Number             = 0.0;
    private var mPreviousPositionOffset:Number    = 0.0;
    private function update():void
    {
        var time:int = getTimer();
        var dt:Number = time - mPreviousTime;
            
        if (dt == 0.0)
            mScratchingPositionVelocity = 0.0;
        else
            mScratchingPositionVelocity =    (mPosition - mScratchingPositionOffset) / dt;
    
        mPreviousTime = time;
    }
        
    private function setByteOffset(offset:Number):void
    {
        mPosition = offset / mAudioSampleSize;
    }
        
    private function initDisplay():void
    {
        mDisplay = new Shape();
        mDisplay.graphics.beginFill(0x0, 0);
        mDisplay.graphics.lineStyle(0, 0x00FF00);
        mDisplay.graphics.drawRect(0, 0, 300, 150);
        mDisplay.graphics.lineStyle(0, 0xFF0000);
        mDisplay.graphics.moveTo(150, 0);
        mDisplay.graphics.lineTo(150, 150);
        mDisplay.graphics.endFill();
            
        var waveMask:Shape = new Shape();
        waveMask.graphics.beginFill(0xFF00FF);
        waveMask.graphics.drawRect(0, 0, 300, 150);
        waveMask.graphics.endFill();
            
        mCenterX = mDisplay.width >> 1;
            
        mWaveHolder.y = mDisplay.height >> 1;
        mWaveHolder.x = mCenterX;
        addChild(mWaveHolder);
            
        var r:Rectangle = new Rectangle( -mWaveHolder.width + mCenterX +2, mWaveHolder.y, mWaveHolder.width -4, 0);
        mFarLeft = r.left;
        mFarRight = r.right;
            
        mWaveHolder.mask = waveMask;
        addChild(waveMask);
            
        addChild(mDisplay);
    }
        
    private function drawWaveForm():void
    {
        mSoundData = new ByteArray();
            
        var max:Number = PLAYBACK_FREQUENCY * (mSound.length / 1000) - WRITE_SIZE;
        mSound.extract(mSoundData, max);
        mSoundData.position = 0;
            
        var yr:int = 35;
        var step:int = 1;
        var xp:int = 0;
            
        var waveform:Shape = new Shape();
        waveform.graphics.lineStyle(0, 0x00FF00);
            
        while(mSoundData.bytesAvailable > PLAYBACK_FREQUENCY * 2)
        {
            var minLeft:Number    = Number.MAX_VALUE;
            var minRight:Number    = Number.MAX_VALUE;
            var maxRight:Number = Number.MIN_VALUE;
            var maxLeft:Number = Number.MIN_VALUE;
                
            for (var i:uint = 0; i < 1024; i++)
            {                                                    
                var left:Number = mSoundData.readFloat();
                if (left > maxLeft) maxLeft    = left;
                if (left < minLeft) minLeft = left;
                var right:Number = mSoundData.readFloat();
                if (right > maxRight) maxRight = right;
                if (right < minRight) minRight = right;            
            }
                
            minLeft        *= yr;
            minRight    *= yr;
            maxLeft        *= yr;
            maxRight    *= yr;
                
            waveform.graphics.moveTo(xp, minLeft);
            waveform.graphics.lineTo(xp, maxLeft);
            xp++;
                
            waveform.graphics.moveTo(xp, minRight);
            waveform.graphics.lineTo(xp, maxRight);
            xp++;
        }
        waveform.cacheAsBitmap = true;
        mWaveHolder.graphics.beginFill(0x0);
        mWaveHolder.graphics.drawRect(0, -waveform.height * .5, waveform.width, waveform.height);
        mWaveHolder.graphics.endFill();
        mWaveHolder.cacheAsBitmap = true;
        mWaveHolder.addChild(waveform);
    }
        
    private function extractSamples():void
    {
        mNumSamples = Math.floor(mSoundData.length / 4);
        mSamples = new Vector.<Number>(mNumSamples, true);
        mSoundData.position = 0;
        for (var i:int = 0; i < mNumSamples; i++)
        {
            mSamples[i] = Number(mSoundData.readFloat());
        }
    }
        
    private function slide(event:Event):void
    {
        mWaveHolder.x += mVelX;
        if (mWaveHolder.x < mFarLeft)    mVelX *= -1;
        if (mWaveHolder.x > mFarRight)    mVelX *= -1;
        
        setSpeed();
            
        mVelX *= DRAG;
            
        // scratch is complete
        if (Math.abs(mVelX) <= .025)
        {
            removeEventListener(Event.ENTER_FRAME, slide);
            completeScratch();
        }
    }
        
    private function completeScratch():void
    {
        mRatio = (mWaveHolder.x - mCenterX) / mWaveHolder.width;
        mPosition = -mRatio * mNumSamples;
        mPosition = clamp(mPosition, 0, mNumSamples - 1);
        mSpeed = 2.0;
        mIsScratching = false;
    }
        
    private function setSpeed():void
    {
        mTargetSpeed = mVelX * SPEED_MULT;
        mSpeed += (mTargetSpeed - mSpeed) / SPEED_EASE;
        mSpeed *= -1;
    }
}