Dynamic VU Meter Animation

Raise your hand if you’ve ever had to build a silly little VU (Volume Unit) meter animation for a “sound on/off” toggle… you know the animation that I mean: those little dancing bars representing sound levels on a stereo? While they’re really easy to animate with the timeline, they’re laborious and they create library overhead.

So, for a recent project I finally just wrote out a dynamic VU Meter animation as an AS class. Instance it, drop it onto the stage, and presto! You’ve got dancing bars. This also lets you smoothly transition between dancing bars and a flat line state by toggling the class’ “enabled” property.

Here it is:

package
{
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;

    /**
    * Constructs a fake volume unit meter which can be toggled between dancing bars and a flatline display.
    * Extremely useful for audio toggle buttons when you need an animation to go with the enabled state.
    */
    public class VolumeUnitMeter extends Sprite
    {
        /**
        * Enables / disables the animation.
        * While enabled, the bars will randomly jump up and down.
        * While disabled, the bars will flatline to the minimum height.
        */
        public var enabled:Boolean = true;

        private var _bars:Array;
        private var _from:Array;
        private var _to:Array;
        private var _percent:Number = 0;
        private var _flat:Number = 0.05;

        /**
        * Constructs a fake volume unit meter display (dancing bars).
        * @param numBars Number of bars in the display.
        * @param barWidth Width of each bar.
        * @param maxHeight Maximum height of a bar.
        * @param minHeight Minimum height of a bar during animation or in flat-line state.
        * @param barSpacing Spacing between each bar display.
        * @param barColor Color of the bars.
        */
        public function VolumeUnitMeter($numBars:int=6, $barWidth:Number=3, $maxHeight:Number=25, $minHeight:Number=1, $barSpacing:Number=2, $barColor:uint=0x000000):void
        {
            super();
            _bars = new Array();
            _from = new Array();
            _to = new Array();
            _flat = $minHeight / $maxHeight;

            // draw bar sprite
            for (var $j:int=0; $j < $numBars; $j++)
            {
                var $bar:Sprite = new Sprite();
                $bar.x = ($barWidth + $barSpacing) * $j;
                $bar.y = $maxHeight;
                $bar.graphics.beginFill($barColor, 1);
                $bar.graphics.drawRect(0, 0, $barWidth, -$maxHeight);
                $bar.graphics.endFill();
                addChild($bar);
                _bars.push($bar);
                _from.push(0);
                _to.push(1);
            }

            // draw hit area
            graphics.beginFill(0x000000, 0);
            graphics.drawRect(0, 0, ($barWidth + $barSpacing) * $numBars, $maxHeight);
            graphics.endFill();
            buttonMode = true;
            useHandCursor = true;
            mouseChildren = false;

            _refreshAnimVars();
            addEventListener(Event.ENTER_FRAME, this._onEnterFrame, false, 0, true);
            addEventListener(MouseEvent.CLICK, this._onClick, false, 0, true);
        }

        /** Assigns a new set of animation target percentages. */
        private function _refreshAnimVars():void
        {
            _percent = 0;
            _from = _to;
            _to = new Array();

            for (var $j:int=0; $j < _bars.length; $j++) {
                _to.push(enabled ? Math.max(Math.random(), _flat) : _flat);
            }
        }

        /** @private called upon each EnterFrame cycle */
        private function _onEnterFrame($event:Event):void
        {
            // animate all equalizer bars.
            for (var $j:int = 0; $j < _bars.length; $j++)
            {
                var $from:Number = _from[$j];
                var $to:Number = _to[$j];
                var $bar:Sprite = _bars[$j];
                $bar.scaleY = $from + (($to - $from) * _percent);
            }

            // update animation percentage.
            if (_percent < 1) {
                _percent += 0.20;
            }
            else {
                _refreshAnimVars();
            }
        }

        /** @private called upon mouseClick of the display */
        private function _onClick($event:Event):void {
            enabled = !enabled;
        }
    }
}

5 comments so far

  1. Henrik Andersson on

    Why? why? why? This is just a lame forgery of the real thing. If this was just another as 2 chunk, I wouldn’t skip a beat, but in as 3? Seriously, this is not excusable, the means for the real thing is there.
    Or in less confusing terms: _refreshAnimVars is just making up the values instead of properly calculating them.

    • bigmac on

      Why? Because when you need to build a volume toggle button in two minutes as part of a tiny little audio player that you’re only getting paid for 2 hours to build, then its not worth spending 10 hours on a truly dynamic button. Just because something is technically possible does not mean that it should be implemented for every project… overkill is a very real consideration, especially when you’re trying to remain profitable.

      • mike on

        I think he means it would have actually been faster to use soundCompute to make it real instead of random. Yours took 2 hours, this took 5 mins and responds to actual eq.

        var s:Sound = new SoundFromLibraryOrUseURL();
        var ba:ByteArray = new ByteArray();

        var gr:Sprite = new Sprite();
        gr.x = 20;
        gr.y = 200;
        addChild(gr);

        var time:Timer = new Timer(50);
        time.addEventListener(TimerEvent.TIMER, timerHandler);
        time.start();

        function timerHandler(event:TimerEvent):void {
        SoundMixer.computeSpectrum(ba, true);
        var i:int;
        gr.graphics.clear();
        gr.graphics.lineStyle(0, 0xFF0000);
        gr.graphics.beginFill(0xFF0000);
        gr.graphics.moveTo(0, 0);
        var w:uint = 2;
        for (i=0; i<512; i+=w) {
        var t:Number = ba.readFloat();
        var n:Number = (t * 100);
        gr.graphics.drawRect(i, 0, w, -n);
        }
        }

  2. Fabio on

    Dear author,

    I saw a your post on a forum with an interesting class developed by you about a Sound Meter in Action Script.

    I would to know if you have a FLA source to see an example or something else to download because I’m going crazy with Sound Meters in Flash.

    Thanks in advance!

    Fabio

    • bigmac on

      Fabio,

      Everything you need is in this blog post. Do this:

      1) Copy the preformatted AS3 class text above and paste it into a new ActionScript document. Save that document as “VolumeUnitMeter.as”

      2) Create a new FLA document and save it into the same local directory as “VolumeUnitMeter.as”. Then, add the following two lines of AS into the new FLA’s frame-1 actions:

      import VolumeUnitMeter;
      addChild( new VolumeUnitMeter() );

      That’s it!


Leave a reply to Henrik Andersson Cancel reply