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

Xali: musical avoiDance

A submission for the Kongregate: Experience the Music competition.
Please try it out at http://www.kongregate.com/games/excessdenied/xali-musical-avoidance
and then vote and give your comments!
Somehow, wonderfl's (this) version sounds different, but the source code is the same!
/*
Xali: musical avoiDance
A submission for the Kongregate: Experience the Music competition.
Please try it out at http://www.kongregate.com/games/excessdenied/xali-musical-avoidance
and then vote and give your comments!
Somehow, wonderfl's (this) version sounds different, but the source code is the same!
*/
package  
{
    import com.bit101.components.PushButton;
    
    import flash.display.Loader;
    import flash.display.LoaderInfo;
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.net.URLRequest;
    import flash.system.Security;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    
    [SWF(width='465',height='465',backgroundColor='0x000000',frameRate='40')]
    public class Xali extends MovieClip {
        
        protected var world:MusicalWorld;
        protected var textField:TextField;
        protected var button:PushButton;
        protected var kongregate:*;
        protected var username:String;
        
        protected const LABEL_START:String = 'Face the music';
        protected const LABEL_STOP:String = 'Give it up';
        protected const TITLE:String = 'xali:\n<font size="24">musical avoiDance</font>';
        
        public function Xali() {
            addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
        }
        
        protected function onAddedToStage(event:Event):void {
            removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
            
            loadKongregate();
            
            world = new MusicalWorld();
            world.scaleX = world.scaleY = 1;
            //world.x = (465-World.WIDTH*World.M2PX*world.scaleX)/2;
            //world.y = (465-World.HEIGHT*World.M2PX*world.scaleY)/2;
            addChild(world);
            world.startWorld();
            
            textField = new TextField();
            var textFormat:TextFormat = textField.getTextFormat();
            textFormat.font = '_typewriter';
            textFormat.color = 0xeeeeee;
            textFormat.size = 32;
            textField.selectable = false;
            textField.defaultTextFormat = textFormat;
            textField.x = World.WIDTH*World.M2PX+10;
            textField.autoSize = TextFieldAutoSize.LEFT;
            textField.wordWrap = true;
            textField.width = 465-World.WIDTH*World.M2PX-10;
            addChild(textField);
            textField.htmlText = TITLE
                +'<font size="12">\n'+((username==null) ? '' : 'for '+username)+'</font>';
            
            button = new PushButton(this, World.WIDTH*World.M2PX+10, World.HEIGHT/2*World.M2PX+10, LABEL_START);
            button.addEventListener(MouseEvent.CLICK, onButtonClick);
            button.width = 465 - World.WIDTH*World.M2PX - 20;
        }
        
        protected function loadKongregate():void {
            // Pull the API path from the FlashVars
            var paramObj:Object = LoaderInfo(root.loaderInfo).parameters;
            
            // The API path. The "shadow" API will load if testing locally.
            var apiPath:String = paramObj.kongregate_api_path ||
                "http://www.kongregate.com/flash/API_AS3_Local.swf";
            
            // Allow the API access to this SWF
            Security.allowDomain(apiPath);
            
            // Load the API
            var request:URLRequest = new URLRequest(apiPath);
            var loader:Loader = new Loader();
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onKongregateLoadComplete);
            loader.load(request);
            this.addChild(loader);
        }
        
        protected function saveKongregate():void {
            if (kongregate == null) return;
            kongregate.stats.submit("MaxTimeMillis", int(world.totalTimeSteps*world.timeStep*1000));
        }
        
        // This function is called when loading is complete
        protected function onKongregateLoadComplete(event:Event):void {
            // Save Kongregate API reference
            kongregate = event.target.content;
            
            // Connect to the back-end
            kongregate.services.connect();
            
            // You can now access the API via:
            // kongregate.services
            // kongregate.user
            // kongregate.scores
            // kongregate.stats
            // etc...
            
            // Listen for a guest->user conversion, which can happen without a refresh
            kongregate.services.addEventListener("login", onKongregateInPageLogin);
        }
        
        protected function onKongregateInPageLogin(event:Event):void {
            // Get the user's new login credentials
            var user_id:Number = kongregate.services.getUserId();
            username = kongregate.services.getUsername();
            var token:String = kongregate.services.getGameAuthToken();
            
            // Log in with new credentials here
            
        }
        
        protected function onButtonClick(event:MouseEvent):void {
            if (button.label == LABEL_START) {
                startWorld();
            } else {
                stopWorld();
            }
        }
        
        protected function onPlayerDestroyed(event:PlayerEvent):void {
            kongregate.stats.submit("NumDeaths", 1);
            stopWorld();
            //onEnterFrame(event);
            //removeEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
        
        protected function onEnterFrame(event:Event):void {
            var seconds:Number = world.totalTimeSteps*world.timeStep;
            textField.htmlText = ''
                +'<font size="24">time:</font>'
                +'\n'+sprintf('%02d:%02d.%01d', int(seconds/60), int(seconds%60), (seconds-int(seconds))*10)
                +'\n<font size="24">lives:</font>'
                +'\n'+world.health;
            
            if (world.totalTimeSteps%(1/world.timeStep*5)==0) {
                saveKongregate();
            }
        }
        
        private function startWorld():void {
            if (world != null) {
                world.stopWorld();
                removeChild(world);
            }
            world = new MusicalWorld();
            world.scaleX = world.scaleY = 1;
            //world.x = (465-World.WIDTH*World.M2PX*world.scaleX)/2;
            //world.y = (465-World.HEIGHT*World.M2PX*world.scaleY)/2;
            addChild(world);
            world.startWorld();
            world.createPlayer();
            addEventListener(Event.ENTER_FRAME, onEnterFrame);
            world.addEventListener(PlayerEvent.PLAYER_DESTROYED, onPlayerDestroyed);
            button.label = LABEL_STOP;
            
            kongregate.stats.submit("NumStarts", 1);
        }
        
        private function stopWorld():void {
            if (world == null) return;
            world.destroyPlayer();
            world.removeEventListener(PlayerEvent.PLAYER_DESTROYED, onPlayerDestroyed);
            removeEventListener(Event.ENTER_FRAME, onEnterFrame);
            var seconds:Number = world.totalTimeSteps*world.timeStep;
            textField.htmlText = TITLE
            +'<font size="12">\n'+((username==null) ? '  ' : 'for '+username)+'</font>'
            +'<font size="24">\n\nlastTime:</font>'
            +'\n'+sprintf('%02d:%02d.%01d', int(seconds/60), int(seconds%60), (seconds-int(seconds))*10)
            button.label = LABEL_START;
            
            saveKongregate();
        }
    }
}
import Box2D.Collision.Shapes.b2FilterData;
import Box2D.Collision.b2ContactPoint;
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.b2Body;
import org.si.sound.events.SoundObjectEvent;

import com.actionsnippet.qbox.QuickBox2D;
import com.actionsnippet.qbox.QuickContacts;
import com.actionsnippet.qbox.QuickObject;
import com.greensock.TweenLite;
import com.greensock.TweenMax;
import com.greensock.TweenNano;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.GradientType;
import flash.display.MovieClip;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.filters.BlurFilter;
import flash.filters.ColorMatrixFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.ui.Keyboard;
import flash.ui.Mouse;
import flash.ui.MouseCursor;
import flash.utils.Dictionary;

import flashx.textLayout.formats.Category;

import org.si.sion.SiONDriver;
import org.si.sion.SiONVoice;
import org.si.sion.sequencer.SiMMLTrack;
import org.si.sion.utils.Scale;
import org.si.sion.utils.SiONPresetVoice;
import org.si.sound.BassSequencer;
import org.si.sound.ChordPad;
import org.si.sound.DrumMachine;
import org.si.sound.synthesizers.AnalogSynth;
import org.si.sound.synthesizers.WaveTableSynth;

class Category {
    public static const NONE:Number = 0x0000;
    public static const ALL:Number = 0xffff;
    public static const PLAYER:Number = 0x0001;
    public static const ENEMY:Number = 0x0002;
    public static const WALL:Number = 0x0004;
    public static const BULLET:Number = 0x0008;
}

class World {
    public static const PX2M:Number = 1/30; // metres/pixel
    public static const M2PX:Number = 1/PX2M;
    public static const DEG2RAD:Number = Math.PI/180; // radians/degree
    public static const WIDTH:Number = 310 * World.PX2M;
    public static const HEIGHT:Number = 465 * World.PX2M;
    public static const BPM:Number = 120;
    public static const STEPS_PER_SEC:int = 40;
}

class Track {
    public static const BULLET:int = 0;
    public static const BULLET_HIT:int = 10;
    public static const ENEMY_TOPLEFT:int = 1;
    public static const ENEMY_TOPRIGHT:int = 2;
    public static const ENEMY_BOTTOMLEFT:int = 3;
    public static const ENEMY_BOTTOMRIGHT:int = 4;
}

class PlayerEvent extends Event {
    public static const PLAYER_DESTROYED:String = "playerDestroyed";
    public static const ENEMY_DESTROYED:String = "enemyDestroyed";
    
    public function PlayerEvent(type:String) {
        super(type, false, false);
    }
}

class WallFactory {
    public static const SIZE:Number = 20 * World.PX2M;
    public static const TOP:int = 1;
    public static const LEFT:int = 2;
    public static const BOTTOM:int = 4;
    public static const RIGHT:int = 8;
    public static const OTHER:int = 16;
    
    protected var qb2d:QuickBox2D;
    
    public function WallFactory(qb2d:QuickBox2D) {
        this.qb2d = qb2d;
    }
    
    public function shift():Array {
        return getInnerWalls(new Rectangle(0, 0, World.WIDTH, World.HEIGHT), SIZE);
    }
    
    protected function getInnerWalls(bounds:Rectangle, thickness:Number):Array {
        var walls:Array = [];
        var wall:QuickObject;
        // top
        wall = getWall(new Rectangle(bounds.left, bounds.top, bounds.width, thickness), TOP);
        walls.push(wall);
        // left
        wall = getWall(new Rectangle(bounds.left, bounds.top, thickness, bounds.height), LEFT);
        walls.push(wall);
        // bottom
        wall = getWall(new Rectangle(bounds.left, bounds.bottom-thickness, bounds.width, thickness), BOTTOM);
        walls.push(wall);
        // right
        wall = getWall(new Rectangle(bounds.right-thickness, bounds.top, thickness, bounds.height), RIGHT);
        walls.push(wall);
        return walls;
    }
    
    protected function getWall(bounds:Rectangle, type:int=OTHER):QuickObject {
        return qb2d.addBox({
            x: (bounds.left+bounds.right)/2,
            y: (bounds.top+bounds.bottom)/2,
            width: bounds.width,
            height: bounds.height,
            density: 0,
            restitution: 0,
            friction: 0,
            categoryBits: Category.WALL,
            maskBits: Category.ALL,
            fillColor: 0x666666,
            fillAlpha: 1,
            lineColor: 0xffffff,
            lineAlpha: 0,
            lineThickness: 4,
            type: type
        });
    }
}

class PlayerFactory {
    public static const SIZE:Number = 20 * World.PX2M;
    public static const SPEED:Number = 150 * World.PX2M;
    protected var qb2d:QuickBox2D;
    
    public function PlayerFactory(qb2d:QuickBox2D) {
        this.qb2d = qb2d;
    }
    
    public function shift():QuickObject {
        var skin:Sprite = new Sprite();
        skin.graphics.lineStyle(SIZE/4*World.M2PX, 0x999999, 0.5);
        skin.graphics.beginFill(0xffffff, 1);
        skin.graphics.drawCircle(0, 0, SIZE/2*World.M2PX);
        skin.graphics.endFill();
        skin.x = World.WIDTH/2 * World.M2PX;
        skin.y = World.HEIGHT/2 * World.M2PX;
        qb2d.main.addChild(skin);
        var core:QuickObject = qb2d.addCircle({
            isSleeping: false,
            allowSleep: false,
            x: World.WIDTH/2,
            y: World.HEIGHT*3/4,
            //width: SIZE,
            //height: SIZE,
            radius: SIZE/4,
            density: 1,
            friction: 1,
            restitution: 0,
            linearDamping: 0,
            draggable: true,
            fixedRotation: true,
            categoryBits: Category.PLAYER,
            maskBits: Category.WALL|Category.ENEMY,
            skin: skin,
            lineColor: 0xFF5555,
            lineThickness: 0,
            lineAlpha: 0,
            fillColor: 0xffffff,
            fillAlpha: 1,
            health: 5,
            score: 0
        });
        return core;
    }
}

class BulletFactory {
    public static const SIZE:Number = PlayerFactory.SIZE/5;
    public static const SPEED:Number = PlayerFactory.SPEED*2;
    
    protected var qb2d:QuickBox2D;
    
    public function BulletFactory(qb2d:QuickBox2D) {
        this.qb2d = qb2d;
    }
    
    public function shift(x:Number, y:Number, spread:Number):Array {
        var bullets:Array = [];
        var angle:Number = -90*World.DEG2RAD// + (Math.random()*spread*2-spread);
        var speed:Number = SPEED;
        bullets.push(getBullet(x, y, speed, angle));
        for (var i:Number=5*World.DEG2RAD; i<spread; i+=5*World.DEG2RAD) {
            bullets.push(getBullet(x, y, speed, angle-i));
            bullets.push(getBullet(x, y, speed, angle+i));
        }
        return bullets;
    }
    
    protected function getBullet(x:Number, y:Number, speed:Number, angle:Number):QuickObject {
        var velocity:b2Vec2 = new b2Vec2(Math.cos(angle)*speed, Math.sin(angle)*speed);
        var bullet:QuickObject = qb2d.addBox({
            x: x,
            y: y,
            angle: angle,
            width: SIZE*3,
            height: SIZE,
            density: 0.00001,
            isBullet: true,
            categoryBits: Category.BULLET,
            maskBits: Category.ENEMY|Category.WALL,
            fillColor: 0xAA0000,
            lineAlpha: 0,
            damage: 1,
            track: Track.BULLET
        });
        bullet.body.SetLinearVelocity(velocity);
        return bullet;
    }
}

class EnemyFactory {
    public static const SIZE:Number = PlayerFactory.SIZE;
    public static const SPEED:Number = PlayerFactory.SPEED*0.75;
    
    protected var qb2d:QuickBox2D;
    protected var queue:Array;
    protected var saturation:Number;
    
    public function EnemyFactory(qb2d:QuickBox2D) {
        this.qb2d = qb2d;
        queue = [];
        saturation = 1;
    }
    
    public function shift(totalBeats:int = 0):Array {
        if (queue.length==0) {
            pushEmpty();
            pushRandom(totalBeats);
        }
        var enemies:Array = queue.shift();
        if (enemies != null) {
            for each (var enemy:QuickObject in enemies) {
                enemy.body.WakeUp();
                var filter:b2FilterData = enemy.shape.GetFilterData();
                filter.maskBits = Category.PLAYER|Category.WALL|Category.BULLET;
                enemy.shape.SetFilterData(filter);
            }
        }
        return enemies;
    }
    
    protected function pushEmpty():void {
        var count:int = Math.floor(Math.random()*10+1);
        for (var i:int=0; i<count; i++) {
            queue.push([]);
        }
    }
    
    protected function pushRandom(totalBeats:int):void {
        const level:int = qb2d.totalTimeSteps*qb2d.timeStep / 10;
        
        var count:int = Math.floor(Math.random()*5+1)+5;
        var speed:Number = SPEED;
        var accel:Number = (Math.random() < 0.5) ? 0 : speed;
        var velAngle:Number = 90*Math.floor(Math.random()*6+1)/6;
        velAngle *= World.DEG2RAD; // straight down or towards bottom right
        var accelAngle:Number = (Math.random() < 0.5) ? 0 + 2/3*45 : 180 - 2/3*45;
        accelAngle *= World.DEG2RAD; // left or right
        var x:Number = Math.floor(Math.random()*7)/6*(World.WIDTH/2-WallFactory.SIZE-SIZE/2) + WallFactory.SIZE+SIZE/2;
        var y:Number = SIZE/2;
        var radius:Number = (Math.random() < 0.5)
            ? SIZE/2 * Math.min(1.2, Math.floor(Math.random()*9+4)/10)
            : SIZE/2 * Math.min(1+(10-count)/10, Math.floor(Math.random()*level+4)/10);
        
        var pattern:int = Math.random()*level;
        for (var i:int=0; i<count; i++) {
            var enemies:Array = [];
            var topLeft:QuickObject = getStationary(x, y, radius);
            topLeft.params.track = Track.ENEMY_TOPLEFT;
            setVelocity(topLeft, speed, velAngle);
            setAcceleration(topLeft, accel, accelAngle);
            setTrack(topLeft, Track.ENEMY_TOPLEFT);
            var topRight:QuickObject = getReflection(topLeft, new Point(World.WIDTH/2, topLeft.y), 90*World.DEG2RAD);
            setTrack(topRight, Track.ENEMY_TOPRIGHT);
            var bottomLeft:QuickObject = getReflection(topLeft, new Point(topLeft.x, World.HEIGHT/2), 0);
            setTrack(bottomLeft, Track.ENEMY_BOTTOMLEFT);
            var bottomRight:QuickObject = getReflection(topRight, new Point(topRight.x, World.HEIGHT/2), 0)
            setTrack(bottomRight, Track.ENEMY_BOTTOMRIGHT);
            
            if (pattern == 0) {
                // top
                enemies.push(topLeft);
                enemies.push(topRight);
                bottomLeft.destroy();
                bottomRight.destroy();
            } else if (pattern == 1) {
                // left
                enemies.push(topLeft);
                enemies.push(bottomLeft);
                topRight.destroy();
                bottomRight.destroy();
            } else if (pattern == 2) {
                // right
                enemies.push(topRight);
                enemies.push(bottomRight);
                topLeft.destroy();
                bottomLeft.destroy();
            } else if (pattern == 3) {
                // bottom
                enemies.push(bottomLeft);
                enemies.push(bottomRight);
                topLeft.destroy();
                topRight.destroy();
            } else if (pattern == 4) {
                // topLeft, bottomRight
                enemies.push(topLeft);
                enemies.push(bottomRight);
                topRight.destroy();
                bottomLeft.destroy();
            } else if (pattern == 5) {
                // topRight, bottomLeft
                enemies.push(topRight);
                enemies.push(bottomLeft);
                topLeft.destroy();
                bottomRight.destroy();
            } else {
                enemies.push(topLeft);
                enemies.push(topRight);
                enemies.push(bottomLeft);
                enemies.push(bottomRight);
            }
            queue.push(enemies);
        }
    }
    
    protected function setVelocity(enemy:QuickObject, speed:Number, angle:Number):void {
        var velocity:b2Vec2 = new b2Vec2(Math.cos(angle)*speed, Math.sin(angle)*speed);
        enemy.angle = angle;
        enemy.body.SetLinearVelocity(velocity);
    }
    
    protected function setAcceleration(enemy:QuickObject, accel:Number, angle:Number):void {
        enemy.params.force = new b2Vec2(enemy.body.GetMass()*accel);
    }
    
    protected function setTrack(enemy:QuickObject, track:int):void {
        enemy.params.track = track;
    }
    
    protected function getStationary(x:Number, y:Number, radius:Number):QuickObject {
        var params:Object = getDefaultParams();
        params.x = x;
        params.y = y;
        params.radius = radius;
        var enemy:QuickObject = qb2d.addCircle(params);
        return enemy;
    }
    
    protected function getReflection(enemy:QuickObject, offset:Point, angle:Number):QuickObject {
        var reflectOrigin:Matrix = new Matrix(
            Math.cos(2*angle), Math.sin(2*angle),
            Math.sin(2*angle), -Math.cos(2*angle)
        );
        var reflector:Matrix = new Matrix();
        reflector.translate(-offset.x, -offset.y);
        reflector.concat(reflectOrigin);
        reflector.translate(offset.x, offset.y);
        
        var location:Point = reflector.transformPoint(new Point(enemy.x, enemy.y));
        var oldVelocity:b2Vec2 = enemy.body.GetLinearVelocity();
        var velocity:Point = reflectOrigin.transformPoint(new Point(oldVelocity.x, oldVelocity.y));
        var oldForce:b2Vec2 = enemy.params.force;
        var force:Point = (oldForce == null) ? null : reflectOrigin.transformPoint(new Point(oldForce.x, oldForce.y));
        
        var params:Object = shallowCopy(enemy.params);
        params.x = location.x;
        params.y = location.y;
        params.force = (force == null) ? undefined : new b2Vec2(force.x, force.y);
        var reflection:QuickObject = qb2d.addCircle(params);
        reflection.body.SetLinearVelocity(new b2Vec2(velocity.x, velocity.y));
        return reflection;
    }
    
    protected function getDefaultParams():Object {
        saturation += 0.001;
        if (saturation > 1) saturation = 0.5;
        var rgb:Array = HSVtoRGB(Math.random()*360, saturation, 0.8);
        return {
            isSleeping: true,
            density: 1,
            restitution: 1,
            draggable: false,
            categoryBits: Category.ENEMY,
                maskBits: Category.NONE,
                lineColor: rgb[0]<<16 | rgb[1]<<8 | rgb[2]<<0,
                lineThickness: 4,
                fillAlpha: 0,
                health: 1,
                damage: 1
        }
    }
    
    protected function shallowCopy(params:Object):Object {
        var copy:Object = {};
        for (var i:String in params) {
            copy[i] = params[i];
        }
        return copy;
    }
}

class ExplosionFactory {
    protected var flash:BitmapData;
    
    public function ExplosionFactory():void {
        flash = new BitmapData(128, 128, true, 0);
        for (var n:Number=0; n<6.283185307179586; n+=0.004363323129985824) {
            var r:Number = (1-Math.random()*Math.random()) * 63;
            var mat:Matrix = new Matrix();
            mat.createGradientBox(128-r-r, 128-r-r, 0, r, r);
            var shp:Shape = new Shape();
            shp.graphics.clear();
            shp.graphics.lineStyle(1);
            shp.graphics.lineGradientStyle(GradientType.RADIAL, [0x808080,0x808080], [1,0], [0,255], mat);
            shp.graphics.moveTo(63.5, 63.5);
            shp.graphics.lineTo(Math.sin(n)*90+63.5, Math.cos(n)*90+63.5);
            flash.draw(shp, null, null, "add");
        }
    }
    
    public function shift(contact:b2ContactPoint, enemy:QuickObject):DisplayObject {
        var bitmap:Bitmap = new Bitmap(flash);
        var sprite:Sprite = new Sprite();
        sprite.addChild(bitmap);
        bitmap.x = bitmap.y = -64;
        sprite.width = sprite.height = enemy.params.radius*World.M2PX*10;
        sprite.rotation = Math.random()*360;
        sprite.x = contact.position.x*World.M2PX;
        sprite.y = contact.position.y*World.M2PX;
        sprite.alpha = 0;
        TweenMax.to(sprite, 0, {tint:enemy.params.lineColor});
        TweenMax.to(sprite, 0.2, {alpha:0.5});
        TweenMax.to(sprite, 0.2, {alpha:0, overwrite:false, delay:0.2, onComplete:function():void {
            if (sprite.parent != null) sprite.parent.removeChild(sprite);
        }});
        return sprite;
    }
}

class PhysicalWorld extends MovieClip {
    protected var player:QuickObject;
    protected var walls:Dictionary;
    protected var bullets:Dictionary;
    protected var enemies:Dictionary;
    protected var enemyFactory:EnemyFactory;
    protected var bulletFactory:BulletFactory;
    protected var explosionFactory:ExplosionFactory;
    
    protected var invulnerabilityCounter:int;
    protected var qb2d:QuickBox2D;
    protected var contacts:QuickContacts;
    protected var keysDown:Dictionary;
    protected var background:Bitmap;
    protected var clip:MovieClip = new MovieClip();
    
    public function PhysicalWorld() {
        addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
    }
    
    public function get health():int {
        return player.params.health;
    }
    
    public function get score():int {
        return player.params.score;
    }
    
    public function get totalTimeSteps():int {
        return qb2d.totalTimeSteps;
    }
    
    public function get timeStep():Number {
        return qb2d.timeStep;
    }
    
    public function set timeStep(value:Number):void {
        qb2d.timeStep = value;
    }
    
    public function startWorld():void {
        stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
        stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
        addEventListener(MouseEvent.ROLL_OVER, onRollOver);
        addEventListener(MouseEvent.ROLL_OUT, onRollOut);
        qb2d.start();
    }
    
    public function stopWorld():void {
        stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
        stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp);
        removeEventListener(MouseEvent.ROLL_OVER, onRollOver);
        removeEventListener(MouseEvent.ROLL_OUT, onRollOut);
        qb2d.stop();
    }
    
    public function createPlayer():void {
        destroyPlayer();
        player = new PlayerFactory(qb2d).shift();
        qb2d.setMouse(player.x*World.M2PX, player.y*World.M2PX);
        qb2d.createMouse(null);
    }
    
    public function destroyPlayer():void {
        if (player == null) return;
        player.destroy();
        TweenMax.killDelayedCallsTo(player.userData);
        TweenMax.killTweensOf(player.userData);
        player = null;
    }
    
    public function get shieldOn():Boolean {
        return player.params.shieldOn;
    }
    
    public function set shieldOn(value:Boolean):void {
        if (value == true) {
            player.params.shieldOn = true;
            TweenMax.to(player.userData, 0.5, {tint:0x333333});
        } else {
            TweenMax.to(player.userData, 0.5, {removeTint:true, onComplete:function():void {
                player.params.shieldOn = false;
            }});
        }
    }
    
    public function addEnemies():void {
        var enemy:QuickObject;
        var newEnemies:Array;
        newEnemies = enemyFactory.shift();
        for each (enemy in newEnemies) {
            enemies[enemy.body] = enemy;
        }
        onEnemiesAdded(newEnemies);
    }
    
    public function addBullets():void {
        var spread:Number = (player.body.GetLinearVelocity().Length()*World.M2PX > 1) ? player.body.GetLinearVelocity().Length()*World.M2PX/30*World.DEG2RAD : 0;
        var newBullets:Array = bulletFactory.shift(player.x, player.y, spread);
        for each (var bullet:QuickObject in newBullets) {
            bullets[bullet.body] = bullet;
        }
        onBulletsAdded(newBullets);
    }
    
    protected function onAddedToStage(event:Event):void {
        removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
        
        qb2d = new QuickBox2D(clip, {
            debug: false,
            gravityX: 0,
            gravityY: 0,
            frim: true,
            timeStep: 1/World.STEPS_PER_SEC,
            iterations: 20,
            bounds: [0, 0, World.WIDTH, World.HEIGHT]
        });
        contacts = qb2d.addContactListener();
        qb2d.addEventListener(QuickBox2D.STEP, onStep);
        qb2d.addEventListener(Event.RENDER, onRender);
        contacts.addEventListener(QuickContacts.ADD, onCollide);
        
        walls = new Dictionary();
        bullets = new Dictionary();
        enemies = new Dictionary();
        
        explosionFactory = new ExplosionFactory();
        bulletFactory = new BulletFactory(qb2d);
        enemyFactory = new EnemyFactory(qb2d);
        for each (var wall:QuickObject in new WallFactory(qb2d).shift()) {
            walls[wall.body] = wall;
        }
        
        keysDown = new Dictionary();
        
        background = new Bitmap(new BitmapData(World.WIDTH*World.M2PX, World.HEIGHT*World.M2PX, false, 0));
        addChild(background);
        addChild(clip);
    }
    
    protected function onRollOver(event:MouseEvent):void {
        if (player != null) Mouse.hide();
    }
    
    protected function onRollOut(event:MouseEvent):void {
        Mouse.show();
    }
    
    protected function onKeyDown(event:KeyboardEvent):void {
        keysDown[event.keyCode] = true;
    }
    
    protected function onKeyUp(event:KeyboardEvent):void {
        keysDown[event.keyCode] = false;
    }
    
    protected function onCollide(event:Event):void {
        var enemy:QuickObject = find(enemies, contacts.currentPoint);
        var bullet:QuickObject = find(bullets, contacts.currentPoint);
        var wall:QuickObject = find(walls, contacts.currentPoint);
        var player:QuickObject = (player != null) ? ((contacts.inCurrentContact(player)) ? player : null) : null;
        
        if (enemy != null) {
            if (player != null) {
                onEnemyContactPlayer(enemy, player)
            }
            else if (bullet != null) {
                onEnemyContactBullet(enemy, bullet);
            }
            else if (wall != null) {
                onEnemyContactWall(enemy, wall);
            }
        }
        else if (bullet != null) {
            if (wall != null) {
                onBulletContactWall(bullet, wall);
            }
        }
    }
    
    protected function onStep(event:Event):void {
        if (player != null) {
            /*if (qb2d.totalTimeSteps%Math.floor(1/qb2d.timeStep/3) == 0) {
            addBullets();
            if (keysDown[Keyboard.SPACE] === false) delete keysDown[Keyboard.SPACE];
            }*/
            var velocity:b2Vec2 = new b2Vec2();
            if (keysDown[Keyboard.LEFT]) {
                velocity.x = -PlayerFactory.SPEED;
            }
            if (keysDown[Keyboard.RIGHT]) {
                velocity.x = PlayerFactory.SPEED;
            }
            if (keysDown[Keyboard.UP]) {
                velocity.y = -PlayerFactory.SPEED;
            }
            if (keysDown[Keyboard.DOWN]) {
                velocity.y = PlayerFactory.SPEED;
            }
            player.body.SetLinearVelocity(velocity);
            
            if (qb2d.totalTimeSteps>0 && qb2d.totalTimeSteps%Math.floor(1/qb2d.timeStep*60) == 0) {
                player.params.health += 1;
            }
        }
        
        if (qb2d.totalTimeSteps%Math.floor(1/qb2d.timeStep/3) == 0) {
            addEnemies();
        }
        for each (var enemy:QuickObject in enemies) {
            if (enemy.params['force'] is b2Vec2) {
                var force:b2Vec2 = enemy.params['force'];
                enemy.body.ApplyForce(force, enemy.body.GetWorldCenter());
            }
        }
    }
    
    protected function onRender(event:Event):void {
        background.x = 0;
        background.y = 0;
        var bitmap:BitmapData = background.bitmapData;
        //bitmap.scroll(0, 4);
        
        bitmap.applyFilter(bitmap, bitmap.rect, new Point(0, 0), new BlurFilter(4, 4, 4));
        /*bitmap.applyFilter(bitmap, bitmap.rect, new Point(0, 0), new ColorMatrixFilter([
            0.9,0.0,0.0,0.0,0.0,
            0.0,0.9,0.0,0.0,0.0,
            0.0,0.0,0.9,0.0,0.0,
            0.0,0.0,0.0,1.0,0.0
        ]));*/
        
        //bitmap.fillRect(new Rectangle(0, 0, 465, 5), 0);
        for each (var enemy:QuickObject in enemies) {
            bitmap.draw(enemy.userData, new Matrix(1, 0, 0, 1, enemy.x*World.M2PX, enemy.y*World.M2PX));
        }
        if (player != null) {
            bitmap.draw(player.userData, new Matrix(1, 0, 0, 1, player.x*World.M2PX, player.y*World.M2PX));
        }
        background.bitmapData = bitmap;
    }
    
    protected function onPlayerDestroyed(player:QuickObject):void {
        Mouse.show();
        dispatchEvent(new PlayerEvent(PlayerEvent.PLAYER_DESTROYED));
    }
    
    protected function onEnemyDestroyed(enemy:QuickObject):void {
        this.addChild(explosionFactory.shift(contacts.currentPoint, enemy.params.lineColor));
        dispatchEvent(new PlayerEvent(PlayerEvent.ENEMY_DESTROYED));
    }
    
    protected function onPlayerShieldChange():void {}
    
    protected function onEnemyContactPlayer(enemy:QuickObject, player:QuickObject):void {
        if (player.params.shieldOn) return;
        
        player.params['health'] -= enemy.params['damage'];
        if (player.params['health'] <= 0) {
            onPlayerDestroyed(player);
            destroyPlayer();
        }
        else {
            shieldOn = true;
            TweenMax.delayedCall(1, function():void {shieldOn=false});
        }
        //destroy(enemies, enemy);
    }
    
    protected function onEnemyContactBullet(enemy:QuickObject, bullet:QuickObject):void {
        enemy.params['health'] -= bullet.params['damage'];
        if (enemy.params['health'] <= 0) {
            destroy(enemies, enemy);
            onEnemyDestroyed(enemy);
        }
        destroy(bullets, bullet);
    }
    
    protected function onEnemyContactWall(enemy:QuickObject, wall:QuickObject):void {
        this.addChild(explosionFactory.shift(contacts.currentPoint, enemy));
        if (wall.params.type == WallFactory.BOTTOM || wall.params.type == WallFactory.TOP) {
            destroy(enemies, enemy);
        }
    }
    
    protected function onEnemiesAdded(enemies:Array):void {
    }
    
    protected function onBulletContactWall(bullet:QuickObject, wall:QuickObject):void {
        destroy(bullets, bullet);
    }
    
    protected function onBulletsAdded(bullet:Array):void {
    }
    
    private function find(store:Dictionary, contact:b2ContactPoint):QuickObject {
        var body1:b2Body = contact.shape1.GetBody();
        var body2:b2Body = contact.shape2.GetBody();
        return (store[body1] !== undefined) ? store[body1] : store[body2];
    }
    
    private function destroy(store:Dictionary, object:QuickObject):void {
        delete store[object.body];
        object.destroy();
    }
}

class MusicalWorld extends PhysicalWorld {
    private var driver:SiONDriver;
    private var voice:SiONVoice;
    private var scale:Scale;
    private var drum:DrumMachine;
    private var bass:BassSequencer;
    private var voices:SiONPresetVoice; 
    
    public function get bpm():Number {
        return driver.bpm;
    }
    
    public function set bpm(value:Number):void {
        driver.bpm = value;
    }
    
    override public function startWorld():void {
        super.startWorld();
        
    }
    
    override public function stopWorld():void {
        super.stopWorld();
        
        driver.stop();
        driver.stopBackgroundSound();
        drum.stop();
        bass.stop();
    }
    
    override public function createPlayer():void {
        super.createPlayer();
        
        driver.play(null, true);
        driver.fadeIn(1);
        drum.play();
        drum.volume = 0.9;
        bass.play();
        bass.volume = 0.6;
    }
    
    override public function destroyPlayer():void {
        super.destroyPlayer();
        
        driver.fadeOut(1);
    }
    
    override protected function onAddedToStage(event:Event):void {
        super.onAddedToStage(event);
        
        driver = (SiONDriver.mutex == null) ? new SiONDriver() : SiONDriver.mutex;
        driver.bpm = 120;
        driver.autoStop = true;
        driver.noteOnExceptionMode = SiONDriver.NEM_SHIFT;
        
        voices = new SiONPresetVoice();
        scale = new Scale('Amp');
        
        // BassSequencer
        //----------------------------------------
        // The BassSequencer is a monophonic sequencer select bass line pattern by number. You can also apply orignal pattern.
        // BassSequencer は,ベースラインパターンを番号で指定する単音シーケンサです.独自パターンを指定することも出来ます.
        bass = new BassSequencer("");             // in current version(0.60) you have to pass something in 1st argument... sorry
        bass.chordName = "o3Am5";                 // E minor 9th chord on octave 3
        bass.patternNumber = 5;                   // bass line pattern number 6
        bass.changePatternOnNextSegment = false;  // change pattern immediately after changing patternNumber property
        var waveTableSynth:WaveTableSynth = new WaveTableSynth();
        waveTableSynth.color = 0x1203acff;  // wavecolor value
        waveTableSynth.releaseTime = 0.2;   // release time
        bass.synthesizer = waveTableSynth;           // apply synthesizer
        var bassCount:int = 0;
        bass.onEnterFrame = function():void {
            if (player == null) return;
            if (bass.pattern[bassCount++%bass.pattern.length] != null) {
                player.userData.scaleX = player.userData.scaleY = 1;
                TweenMax.to(player.userData, 60/(driver.bpm*8), {scaleX:1.5, scaleY:1.5, yoyo:true, repeat:1});
            }
        };
        
        // DrumMachine
        //----------------------------------------
        // The DrumMachine is a 3 tracks sequencer plays drum patterns specifyed by independent number for each bass, snare and hihat.
        // DrumMachine は,バス,スネア,ハイハットそれぞれに独立した番号でドラムパターンを指定するを3声シーケンサです.
        drum = new DrumMachine();
        drum.bassPatternNumber = 4;   // bass drum pattern number
        drum.snarePatternNumber = 8;  // snare drum pattern number
        drum.hihatPatternNumber = 4;  // hihat drum pattern number
        drum.changePatternOnNextSegment = true;  // don't change pattern immediately after changing ****PatternNumber property
        // In this sample, we use default voices of DrumMachine. You can also apply any voices for each track.
        // このサンプルでは,DrumMachine のデフォルトボイスを使用しています.各トラックに独自のボイスを割り当てることも出来ます.
        // All DrumMachine's default voices are 1 operator voice, so you can play very light-weighted rhythm track.
        // DrumMachine のデフォルトボイスは全て1オペレータで合成するため,非常に軽快にリズムトラックを再生できます.
        drum.bassVoiceNumber = 0;   // bass drum voice number
        drum.snareVoiceNumber = 2;  // snare drum voice number
        drum.hihatVoiceNumber = 0;  // hihat drum voice number
    }
    
    override protected function onStep(event:Event):void {
        super.onStep(event);
        
        if (driver.isPlaying == false || player == null) return;
        var waveTableSynth:WaveTableSynth = bass.synthesizer as WaveTableSynth;
        if (qb2d.totalTimeSteps%20==0) {
            var c8:Number = player.y/World.HEIGHT;
            var c5:Number = player.x/World.WIDTH;
            var col:int = 0;
            col |= (c8<0.75) ? 15 : int((1 - c8) * 60);
            col |= ((c8<0.50) ? (c8*30) : (c8<0.75) ? 15 : int((1 - c8) * 44 + 4))<<4;
            col |= ((c8<0.25) ? 0 : (c8<0.75) ? int((c8-0.25)*30) : int((1 - c8) * 32 + 7))<<12;
            col |= ((c8<0.50) ? 0 : int((c8-0.5)*30))<<24;
            col |= ((c5<0.50) ? int(c5*30) : 15)<<8;
            col |= ((c5<0.50) ? 0 : int((c5-0.5)*30))<<20;    
            if (waveTableSynth.color != col) {
                waveTableSynth.color = col;
            }
        }
        if (qb2d.totalTimeSteps > 0 && qb2d.totalTimeSteps%(1/qb2d.timeStep*30) == 0) {
            drum.bassPatternNumber = Math.random()*drum.bassPatternNumberMax;
            drum.snarePatternNumber = Math.random()*14; //drum.snarePatternNumberMax;
            drum.hihatPatternNumber = Math.random()*drum.hihatPatternNumberMax;
            bass.patternNumber = Math.random()*bass.patternNumberMax;
            //driver.bpm += 10;
            //qb2d.timeStep = 3/driver.bpm;
        }
        /*if (qb2d.totalTimeSteps%int(1/qb2d.timeStep/3) == 0) {
            var track:SiMMLTrack = driver.noteOn(scale.getNote(-10), voices['midi.lead5'], 1, 0, 1, Track.BULLET);
            track.pan = 128*player.x/(World.WIDTH) - 64;
            track.velocity = 48;
        }*/
        
    }
    
    override protected function onPlayerDestroyed(player:QuickObject):void {
        super.onPlayerDestroyed(player);
        
        driver.fadeOut(1);
    }
    
    override protected function onEnemyDestroyed(enemy:QuickObject):void {
        super.onEnemyDestroyed(enemy);
        
        if (player != null) player.params.score += driver.bpm;
    }
    
    override protected function onEnemyContactWall(enemy:QuickObject, wall:QuickObject):void {
        super.onEnemyContactWall(enemy, wall);
        
        if (driver.isPlaying == false) return;
        var track:SiMMLTrack = driver.noteOn(scale.getNote(getScaleIndex(enemy)), voices['midi.lead8'], 1, 0, 1, enemy.params.track);
        track.pan = 128*enemy.x/World.WIDTH - 64;
        //track.velocity = 80 * enemy.body.GetMass()*enemy.body.GetLinearVelocity().Length()/EnemyFactory.SPEED/EnemyFactory.SIZE/EnemyFactory.SIZE;
        track.velocity = 64 * enemy.body.GetLinearVelocity().Length()/EnemyFactory.SPEED;
        //track.velocity = 36 * enemy.params.radius*2/EnemyFactory.SIZE;
    }
    
    override protected function onEnemiesAdded(enemies:Array):void {
        super.onEnemiesAdded(enemies);
        
        if (driver.isPlaying == false) return;
        for each (var enemy:QuickObject in enemies) {
            var track:SiMMLTrack = driver.noteOn(scale.getNote(getScaleIndex(enemy)-5), voices['midi.lead5'], 1, 0, 1, Track.BULLET_HIT);
            track.pan = 128*enemy.x/World.WIDTH - 64;
            track.velocity = 64 * enemy.body.GetLinearVelocity().Length()/EnemyFactory.SPEED;
            //track.velocity = 80 * enemy.params.radius*2/EnemyFactory.SIZE;
        }
    }
    
    /*override protected function onBulletsAdded(bullets:Array):void {
        super.onBulletsAdded(bullets);
        
        if (driver.isPlaying == false) return;
        var track:SiMMLTrack = driver.noteOn(scale.getNote(-10), voices['midi.lead5'], 1, 0, 1, Track.BULLET);
        track.pan = 128*bullets[int(bullets.length/2)].x/(World.WIDTH) - 64;
        track.velocity = 48;
    }
    
    override protected function onEnemyContactBullet(enemy:QuickObject, bullet:QuickObject):void {
        super.onEnemyContactBullet(enemy, bullet);
        
        if (driver.isPlaying == false) return;
        var track:SiMMLTrack = driver.noteOn(scale.getNote(getScaleIndex(enemy)), voices['midi.lead7'], 1, 0, 1, Track.BULLET_HIT);
        track.pan = 128*enemy.x/World.WIDTH - 64;
        track.velocity = 36 * enemy.body.GetLinearVelocity().Length()/EnemyFactory.SPEED;
    }*/
    
    protected function getScaleIndex(enemy:QuickObject):int {
        var r:int = (enemy.params.lineColor&0xff0000) >> 16;
        var g:int = (enemy.params.lineColor&0x00ff00) >> 8;
        var b:int = (enemy.params.lineColor&0x0000ff);
        var scaleIndex:int = enemy.params.lineColor * 5/0xffffff;
        if (r > g && r > b) {
            if (g > b) scaleIndex = 0;
            else scaleIndex = 1;
        } else if (g > r && g > b) {
            if (r > b) scaleIndex = 2;
            else scaleIndex = 3;
        } else {
            if (r > g) scaleIndex = 4;
            else scaleIndex = 5;
        }
        return scaleIndex;
    }
}

function sprintf(raw : String, ...rest) : String{
    /**
     * Pretty ugly!
     *   basicaly
     *   % -> the start of a substitution hole
     *   (some_var_name) -> [optional] used in named substitutions
     *   .xx -> [optional] the precision with witch numbers will be formated  
     *   x -> the formatter (string, hexa, float, date part)
     */
    var SUBS_RE : RegExp = /%(?!^%)(\((?P<var_name>[\w]+[\w_\d]+)\))?(?P<padding>[0-9]{1,2})?(\.(?P<precision>[0-9]+))?(?P<formater>[sxofaAbBcdHIjmMpSUwWxXyYZ])/ig;
    
    //Return empty string if raw is null, we don't want errors here
    if( raw == null ){
        return "";    
    }
    //trace("\n\n" + 'input:"'+ raw+  '" , args:', rest.join(", ")) ;
    var matches : Array = [];
    var result : Object = SUBS_RE.exec(raw);
    var match : Match;
    var runs : int = 0;
    var numMatches : int = 0;
    var numberVariables : int = rest.length;
    // quick check if we find string subs amongst the text to match (something like %(foo)s
    var isPositionalSubts : Boolean = !Boolean(raw.match(/%\(\s*[\w\d_]+\s*\)/));
    var replacementValue : *;
    var formater : String;
    var varName : String;
    var precision : String;
    var padding : String;
    var paddingNum : int;
    var paddingChar:String;
    
    // matched through the string, creating Match objects for easier later reuse
    while (Boolean(result)){
        match = new Match();
        match.startIndex = result.index;
        match.length = String(result[0]).length;
        match.endIndex = match.startIndex + match.length;
        match.content = String(result[0]);
        // try to get substitution properties
        formater = result.formater;
        varName = result.var_name;
        precision = result.precision;
        padding = result.padding;
        //trace('formater:', formater, ', varName:', varName, ', precision:', precision, 'padding:', padding);
        if (padding){
            if (padding.length == 1){
                paddingNum = int(padding);
                paddingChar = " ";
            }else{
                paddingNum = int (padding.substr(-1, 1));
                paddingChar = padding.substr(-2, 1)
                if (paddingChar != "0"){
                    paddingNum *= int(paddingChar);
                    paddingChar = " "
                }
            } 
        }
        if (isPositionalSubts){
            // by position, grab next subs:
            replacementValue = rest[matches.length];
            
        }else{
            // be hash / properties 
            replacementValue = rest[0] == null ? undefined : rest[0][varName];
        }
        // check for bad variable names
        if (replacementValue == undefined){
            replacementValue = "";
        }
        if (replacementValue != undefined){
            
            // format the string accodingly to the formatter
            if (formater == STRING_FORMATTER){
                match.replacement = padString(replacementValue.toString(), paddingNum, paddingChar);
            }else if (formater == FLOAT_FORMATER){
                // floats, check if we need to truncate precision
                if (precision){
                    match.replacement = padString( 
                        Number(replacementValue).toFixed( int(precision)),
                        paddingNum, 
                        paddingChar);
                }else{
                    match.replacement = padString(replacementValue.toString(), paddingNum, paddingChar);
                }
            }else if (formater == INTEGER_FORMATER){
                match.replacement = padString(int(replacementValue).toString(), paddingNum, paddingChar);
            }else if (formater == OCTAL_FORMATER){
                match.replacement = "0" + int(replacementValue).toString(8);
            }else if (formater == HEXA_FORMATER){
                match.replacement = "0x" + int(replacementValue).toString(16);
            }else if(DATES_FORMATERS.indexOf(formater) > -1){
                switch (formater){
                    case DATE_DAY_FORMATTER:
                        match.replacement = replacementValue.date;
                        break
                    case DATE_FULLYEAR_FORMATTER:
                        match.replacement = replacementValue.fullYear;
                        break
                    case DATE_YEAR_FORMATTER:
                        match.replacement = replacementValue.fullYear.toString().substr(2,2);
                        break
                    case DATE_MONTH_FORMATTER:
                        match.replacement = replacementValue.month + 1;
                        break
                    case DATE_HOUR24_FORMATTER:
                        match.replacement = replacementValue.hours;
                        break
                    case DATE_HOUR_FORMATTER:
                        var hours24 : Number = replacementValue.hours;
                        match.replacement =  (hours24 -12).toString();
                        break
                    case DATE_HOUR_AMPM_FORMATTER:
                        match.replacement =  (replacementValue.hours  >= 12 ? "p.m" : "a.m");
                        break
                    case DATE_TOLOCALE_FORMATTER:
                        match.replacement = replacementValue.toLocaleString();
                        break
                    case DATE_MINUTES_FORMATTER:
                        match.replacement = replacementValue.minutes;
                        break
                    case DATE_SECONDS_FORMATTER:
                        match.replacement = replacementValue.seconds;
                        break    
                }
            }else{
                //no good replacement
            }
            matches.push(match);
        }
        
        // just a small check in case we get stuck: kludge!
        runs ++;
        if (runs > 10000){
            //something is wrong, breaking out
            break;
        }
        numMatches ++;
        // iterates next match
        result = SUBS_RE.exec(raw);
    }
    // in case there's nothing to substitute, just return the initial string
    if(matches.length == 0){
        //no matches, returning raw
        return raw;
    }
    // now actually do the substitution, keeping a buffer to be joined at 
    //the end for better performance
    var buffer : Array = [];
    var lastMatch : Match;  
    // beggininf os string, if it doesn't start with a substitition
    var previous : String = raw.substr(0, matches[0].startIndex);
    var subs : String;
    for each(match in matches){
        // finds out the previous string part and the next substitition
        if (lastMatch){
            previous = raw.substring(lastMatch.endIndex  ,  match.startIndex);
        }
        buffer.push(previous);
        buffer.push(match.replacement);
        lastMatch = match;
        
    }
    // buffer the tail of the string: text after the last substitution
    buffer.push(raw.substr(match.endIndex, raw.length - match.endIndex));
    //trace('returning: "'+ buffer.join("")+'"');
    return buffer.join("");
}

// internal usage
/** @private */
const BAD_VARIABLE_NUMBER : String = "The number of variables to be replaced and template holes don't match";
/** Converts to a string*/
const STRING_FORMATTER : String = "s";
/** Outputs as a Number, can use the precision specifier: %.2sf will output a float with 2 decimal digits.*/
const FLOAT_FORMATER : String = "f";
/** Outputs as an Integer.*/
const INTEGER_FORMATER : String = "d";
/** Converts to an OCTAL number */
const OCTAL_FORMATER : String = "o";
/** Converts to a Hexa number (includes 0x) */
const HEXA_FORMATER : String = "x";
/** @private */
const DATES_FORMATERS : String = "aAbBcDHIjmMpSUwWxXyYZ";
/** Day of month, from 0 to 30 on <code>Date</code> objects.*/
const DATE_DAY_FORMATTER : String = "D";
/** Full year, e.g. 2007 on <code>Date</code> objects.*/
const DATE_FULLYEAR_FORMATTER : String = "Y";
/** Year, e.g. 07 on <code>Date</code> objects.*/
const DATE_YEAR_FORMATTER : String = "y";
/** Month from 1 to 12 on <code>Date</code> objects.*/
const DATE_MONTH_FORMATTER : String = "m";
/** Hours (0-23) on <code>Date</code> objects.*/
const DATE_HOUR24_FORMATTER : String = "H";
/** Hours 0-12 on <code>Date</code> objects.*/
const DATE_HOUR_FORMATTER : String = "I";
/** a.m or p.m on <code>Date</code> objects.*/
const DATE_HOUR_AMPM_FORMATTER : String = "p";
/** Minutes on <code>Date</code> objects.*/
const DATE_MINUTES_FORMATTER : String = "M";
/** Seconds on <code>Date</code> objects.*/
const DATE_SECONDS_FORMATTER : String = "S";
/** A string rep of a <code>Date</code> object on the current locale.*/
const DATE_TOLOCALE_FORMATTER : String = "c";

var version : String = "$Id$";

/** @private
 * Internal class that normalizes matching information.
 */
class Match{
    public var startIndex : int;
    public var endIndex : int;
    public var length : int;
    public var content : String;
    public var replacement : String;
    public var before : String;
    public function toString() : String{
        return "Match [" + startIndex + " - " + endIndex + "] (" + length + ") " + content + ", replacement:" +replacement + ";"
    }
}

/** @private */
function padString(str:String, paddingNum:int, paddingChar:String=" "):String
{
    if(paddingChar == null) return str;
    
    var i:int;
    var buf:Array = [];
    for (i = 0; i < Math.abs(paddingNum) - str.length; i++)
        buf.push(paddingChar);
    
    if (paddingNum < 0){
        buf.unshift(str);
    }
    else{
        buf.push(str);
    }    
    return buf.join("");
}

/**
 * HSV from each of the RGB values to determine a return as an array.
 * RGB values are as follows.
 * R - a number from 0 to 255
 * G - a number from 0 to 255
 * B - a number from 0 to 255
 *
 * HSV values are as follows.
 * H - a number between 360-0
 * S - number between 0 and 1.0
 * V - number between 0 and 1.0
 *
 * H is replaced with equivalent numbers in the range of the 360-0 that is out of range.
 * Can not compute, including alpha.
 *
 * @ Param h hue (Hue) number that indicates (to 360-0)
 * @ Param s the saturation (Saturation) shows the number (0.0 to 1.0)
 * @ Param v lightness (Value) indicates the number (0.0 to 1.0)
 * @ Return RGB values into an array of [R, G, B]
 **/
function HSVtoRGB (h: Number, s: Number, v: Number): Array
{
    var r: Number = 0, g: Number = 0, b: Number = 0;
    var i: Number, x: Number, y: Number, z: Number;
    if (s <0) s = 0; if (s> 1) s = 1; if (v <0) v = 0; if (v> 1) v = 1;
    h = h% 360; if (h <0) h += 360; h /= 60;
    i = h>> 0;
    x = v * (1 - s); y = v * (1 - s * (h - i)); z = v * (1 - s * (1 - h + i));
    switch (i) {
        case 0: r = v; g = z; b = x; break;
        case 1: r = y; g = v; b = x; break;
        case 2: r = x; g = v; b = z; break;
        case 3: r = x; g = y; b = v; break;
        case 4: r = z; g = x; b = v; break;
        case 5: r = v; g = x; b = y; break;
    }
    return [r * 255>> 0, g * 255>> 0, b * 255>> 0];
}