/**
* Copyright wonderwhyer ( http://wonderfl.net/user/wonderwhyer )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/n1Mj
*/
package{
import Box2D.Dynamics.*
import Box2D.Collision.*
import Box2D.Collision.Shapes.*
import Box2D.Dynamics.Joints.*
import Box2D.Dynamics.Contacts.*
import Box2D.Common.Math.*
import com.bit101.components.ComboBox;
import com.bit101.components.PushButton;
import com.greensock.*;
import com.greensock.easing.Back;
import flash.events.Event;
import flash.display.*;
import flash.events.KeyboardEvent;
import flash.filters.BevelFilter;
import flash.filters.DropShadowFilter;
import flash.filters.GlowFilter;
import flash.geom.Rectangle;
import flash.text.*;
import flash.display.MovieClip;
import flash.events.MouseEvent;
import com.bit101.components.Knob;
import flash.utils.getTimer;
import flash.geom.Point;
import flash.system.Capabilities;
/**
* ...
* @author EduardRuzga www.wonderwhy-er.com
* Game made for LudumDare 21 compo,
* post compo edition with some textures and sounds added
*/
[SWF(width="465", height="465", frameRate="30", backgroundColor="0x000000")]
public class Main extends MovieClip {
public var bodyParts:Array = [];
public var bodyPartsHash:Object = {};
public var debug:TextField;
public var playing:Boolean = true;
public var m_currId:int = 0;
public var sandBox:SandBox;
static public var m_sprite:Sprite;
static public var ui:Sprite;
static public var dragedJoint:Sprite;
public static var _instance:Main;
public var selecting:Boolean = false;
public var selectionStartX:Number = 0;
public var selectionStartY:Number = 0;
[Embed(systemFont="serif", fontWeight="bold", fontName="font1", mimeType="application/x-font", embedAsCFF="false")]
private static var font:Class;
public var glow:GlowFilter = new GlowFilter(0xFF0000);
public var skyTexture:BitmapData;
public var sky:Sprite;
public var startScreen:Sprite;
public var GameName:TextField;
public var GameNameTexture:Sprite;
public var StartGameText:TextField;
public var pause:PushButton;
public var fullScreenBtn:PushButton;
public var soundBtn:PushButton;
public var reStart:PushButton;
public function Main(){
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT
stage.addEventListener(Event.RESIZE, resize);
//StartGame();
startScreen = new Sprite();
addChild(startScreen);
GameName = new TextField();
GameName.defaultTextFormat = new TextFormat("font1", 50, 0xFFFFFF, true);
_instance = this;
GameName.text = "Zero\n Gravity\n Escape";
GameName.autoSize = TextFieldAutoSize.LEFT;
GameName.embedFonts = true;
GameName.selectable = false;
GameNameTexture = new Sprite();
var bmd:BitmapData = Textures.getShipAlloy(100, 100,1);
bmd = Textures.getShipAlloy(100, 100,1);
GameNameTexture.graphics.beginBitmapFill(bmd);
GameNameTexture.graphics.drawRect(0, 0, GameName.width, GameName.height);
GameNameTexture.graphics.endFill();
GameNameTexture.x = GameName.x;
GameNameTexture.y = GameName.y;
startScreen.addChild(GameNameTexture);
startScreen.addChild(GameName);
GameNameTexture.mask = GameName;
StartGameText = new TextField();
StartGameText.defaultTextFormat = new TextFormat("font1", 30, 0xFFFFFF, true);
StartGameText.text = "Click anywhere to start";
startScreen.addChild(StartGameText);
StartGameText.y = GameName.y + GameName.height + 50;
StartGameText.autoSize = TextFieldAutoSize.CENTER;
StartGameText.embedFonts = true;
StartGameText.selectable = false;
StartGameText.x = GameName.x + (GameName.width - StartGameText.width) / 2;
startScreen.x = (stage.stageWidth - startScreen.width) / 2;
startScreen.y = (stage.stageHeight - startScreen.height) / 2;
stage.addEventListener(MouseEvent.CLICK, StartGame);
var t:uint = getTimer();
SFX.init();
trace((getTimer()-t));
}
public function resize(e:Event):void
{
if (startScreen) {
startScreen.x = (stage.stageWidth - startScreen.width) / 2;
startScreen.y = (stage.stageHeight - startScreen.height) / 2;
}
}
public function StartGame(e:Event = null):void
{
removeChild(startScreen);
addEventListener(Event.ENTER_FRAME, update, false, 0, true);
stage.removeEventListener(MouseEvent.CLICK, StartGame);
skyTexture = Textures.getStarSky(500, 500);
sky = new Sprite();
sky.graphics.beginBitmapFill(skyTexture);
sky.graphics.drawRect(0, 0, 1100 + Capabilities.screenResolutionX, 1000 + Capabilities.screenResolutionY);
sky.graphics.endFill();
addChild(sky);
sky.mouseEnabled = sky.mouseChildren = false;
m_sprite = new Sprite();
m_sprite.name = "Engine";
addChild(m_sprite);
m_sprite.mouseEnabled = false;
ui = new Sprite();
ui.mouseEnabled = ui.mouseChildren = false;
addChild(ui);
// input
var hints:TextField = new TextField();
hints.mouseEnabled = false;
hints.autoSize = TextFieldAutoSize.LEFT;
hints.textColor = 0xFFFFFF;
hints.defaultTextFormat = new TextFormat(null, 14);
hints.text = "Story: you are on a space station,\n gravity went off, \nnow you need to get to the button to turn it on again"
+"\n\n Controls:\n- click on joints and drag to set angles\n-click anywhere and drag to select multiple joints and relax or fixate them\n-use space to pause the game for more precise and simulatious commands joint edditing\n-press R to restart";
ui.addChild(hints);
stage.addEventListener(MouseEvent.MOUSE_UP, OnMouseRelease);
debug = new TextField();
debug.autoSize = TextFieldAutoSize.LEFT;
debug.x = 300;
addChild(debug);
stage.addEventListener(KeyboardEvent.KEY_DOWN, OnKeyDown);
stage.addEventListener(MouseEvent.MOUSE_DOWN, OnStageDrag);
pause = new PushButton(this, 5, 5, "Pause(Space)", Pause);
fullScreenBtn = new PushButton(this, pause.x+pause.width + 5, 5, "Fullscreen", fullScreen);
soundBtn = new PushButton(this, fullScreenBtn.x + fullScreenBtn.width + 5, 5, "Turn sounds off", sounds);
reStart = new PushButton(this,soundBtn.x+soundBtn.width + 5, 5, "Restart(R)", Restart);
}
public function sounds(e:Event=null):void
{
SfxrSynth.Mute = !SfxrSynth.Mute;
soundBtn.label = SfxrSynth.Mute?"Turn sounds on":"Turn sounds off";
}
public function fullScreen(e:Event = null):void
{
if (stage.displayState == StageDisplayState.FULL_SCREEN)
{
stage.displayState = StageDisplayState.NORMAL;
fullScreenBtn.label = "Fullscreen";
}
else
{
stage.displayState = StageDisplayState.FULL_SCREEN;
fullScreenBtn.label = "Exit Fullscreen";
}
}
public function Pause(e:Event=null):void
{
playing = !playing;
pause.label = playing?"Pase(Space)":"Play(Space)";
}
public function OnKeyDown(e:KeyboardEvent):void
{
switch(e.keyCode)
{
case 32:
Pause();
break;
case 82:
Restart();
break;
}
}
public function Restart(e:Event = null):void
{
sandBox = null;
pressed = false;
while (m_sprite.numChildren>0)
{
m_sprite.removeChildAt(0);
}
bodyParts = [];
}
public function formatRadians(n:Number):Number
{
return Math.round(n * 180 / Math.PI);
}
public var pressed:Boolean = false;
public function ButtonPress():void
{
if (!pressed)
{
SFX.powerUp.play();
pressed = true;
TweenLite.to(SandBox._instance.gravity, 1, { delay:1, x:0,y:10, ease:Back.easeOut} );
//var t:GTween = new GTween(SandBox._instance.gravity, 1, { }, { delay:1, ease:fl.motion.easing.Back.easeOut } );
//t.proxy.x = 0;
//t.proxy.y = 10;
var spr:Sprite = new Sprite();
spr.graphics.beginFill(0);
spr.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
var theEnd:TextField = new TextField();
theEnd.textColor = 0xFFFFFF;
theEnd.defaultTextFormat = new TextFormat(null, 20);
theEnd.text = "To be conitnued... Hopefully. \nClick to see how clumsy austraunauts are with gravity :D";
theEnd.autoSize = TextFieldAutoSize.LEFT;
theEnd.x = (stage.stageWidth - theEnd.width) / 2;
theEnd.y = (stage.stageHeight - theEnd.height) / 2;
spr.addChild(theEnd);
addChild(spr);
spr.alpha = 0;
TweenLite.to(spr, 1, { delay:0, x:0,y:10, ease:Back.easeOut } );
//t = new GTween(spr, 1, { }, { delay:0, ease:fl.motion.easing.Back.easeOut } );
//t.proxy.x = 0;
//t.proxy.y = 10;
spr.addEventListener(MouseEvent.CLICK, Remove);
TweenLite.to(spr, 1, { delay:2, alpha:1, ease:Back.easeOut } );
//t = new GTween(spr, 1, { }, { delay:2, ease:fl.motion.easing.Back.easeOut } );
//t.proxy.alpha = 1;
}
}
public function Remove(e:Event):void
{
removeChild(e.currentTarget as Sprite);
}
public function update(e:Event):void{
m_sprite.graphics.clear()
// if null, set new test
if (!sandBox) {
sandBox = new SandBox();
var itms:Array = [];
for (var key:String in sandBox.muscles)
{
itms.push(key);
var info:Object = { name:key, "joint":sandBox.muscles[key], "angle":0, "state":false };
bodyPartsHash[key] = info;
bodyParts.push( info );
}
var walls:Sprite = new Sprite();
var texture:BitmapData = Textures.getShipAlloy(100, 100);
var body:b2Body = sandBox.m_world.m_bodyList;
while (body)
{
var rect:Rectangle = body.m_userData as Rectangle;
if (rect != null)
{
walls.graphics.beginBitmapFill(texture);
walls.graphics.drawRect(rect.x, rect.y, rect.width, rect.height);
}
body = body.GetNext();
}
walls.filters = [new DropShadowFilter(0,45,0,1,10,10,1.5,1,true)/*Textures.bvl*/,new GlowFilter(0,1,300,300,2)];
var walls2:Sprite = new Sprite();
walls2.filters = [new DropShadowFilter(0,45,0,1,10,10,1.5,1,true)/*new BevelFilter(5, 45, 0xffffff, 1, 0, 1, 5, 5)*/];
rect = walls.getBounds(walls);
walls2.graphics.beginBitmapFill(Textures.getShipAlloy(100, 100,1));
walls2.graphics.drawRect(rect.x, rect.y, rect.width, rect.height);
walls2.graphics.drawCircle(200, 200, 50 );
walls2.graphics.drawCircle(650, 200, 50 );
walls2.graphics.drawCircle(400, 600, 50 );
walls2.graphics.drawCircle(850, 600, 50 );
walls2.mouseChildren = walls2.mouseEnabled = walls.mouseEnabled = walls.mouseChildren = false;
walls2.cacheAsBitmap = walls.cacheAsBitmap = true;
m_sprite.addChild(walls2);
m_sprite.addChild(walls);
}
ui.graphics.clear();
var scale:Number = 30;
// update current test
if (playing)
{
sandBox.Update();
var headPos:b2Vec2 = (bodyPartsHash["neck"].joint as b2Joint).GetBody1().GetPosition().Copy();
headPos.Multiply(scale);
ui.x = m_sprite.x = -headPos.x+stage.stageWidth/2;
ui.y = m_sprite.y = -headPos.y+stage.stageHeight/2;
sky.x = -headPos.x / 2 - Capabilities.screenResolutionX / 2;
sky.y = -headPos.y / 2 - Capabilities.screenResolutionY / 2;
}
body = sandBox.m_world.m_bodyList;
while (body)
{
if (body.m_userData as Sprite != null)
{
var sp:Sprite = body.m_userData as Sprite;
if(sp){
if (sp.parent != m_sprite)
{
m_sprite.addChild(sp);
}
var pos:b2Vec2 = body.GetPosition();
sp.x = pos.x*scale;
sp.y = pos.y * scale;
sp.rotation = body.GetAngle() * (180 / Math.PI);
}
}
body = body.GetNext();
}
var dir:b2Vec2;
if (dragedJoint)
{
if(!dragedJoint.hitTestPoint(dragedJoint.mouseX,dragedJoint.mouseY,true))
{
var i:uint = int(dragedJoint.name);
var bodyPartInfo:Object = bodyParts[i];
if(!bodyPartInfo.state && (getTimer()-mouseDownTime)>100)
bodyPartInfo.state = true;
var joint:b2RevoluteJoint = bodyPartInfo.joint as b2RevoluteJoint;
var angle:Number;
var vec:b2Vec2 = new b2Vec2(dragedJoint.x - m_sprite.mouseX, dragedJoint.y - m_sprite.mouseY);
var newAngle:Number = Math.atan2(vec.y, vec.x);
var baseVec:b2Vec2 = joint.m_localAnchor2.Copy();
baseVec.Subtract(joint.GetBody2().GetLocalCenter());
var baseAgnle:Number = Math.atan2(baseVec.y, baseVec.x);
//hack some angle problems after too much rotations
if (bodyPartInfo.name.indexOf("Elbow") < 0 && bodyPartInfo.name.indexOf("Shoulder") < 0)
{
baseAgnle += Math.PI;
}
var finalAngle:Number = (newAngle + baseAgnle) - bringDownRadian(joint.GetBody1().GetAngle());
if (finalAngle > Math.PI)
finalAngle -= Math.PI * 2;
else if (finalAngle < -Math.PI)
finalAngle += Math.PI * 2;
finalAngle = Math.max(joint.GetLowerLimit(), Math.min(joint.GetUpperLimit(), finalAngle));
bodyPartInfo.angle = finalAngle;
ui.graphics.lineStyle(1, 0xFF0000);
ui.graphics.moveTo(dragedJoint.x, dragedJoint.y);
ui.graphics.lineTo(m_sprite.mouseX, m_sprite.mouseY);
}
}
var selectionRect:Rectangle;
if (selecting)
{
ui.graphics.lineStyle(1, 0xFFFF00);
selectionRect = new Rectangle(Math.min(m_sprite.mouseX, selectionStartX), Math.min(m_sprite.mouseY, selectionStartY), Math.abs(m_sprite.mouseX - selectionStartX), Math.abs(m_sprite.mouseY - selectionStartY));
ui.graphics.drawRect(selectionRect.x,selectionRect.y,selectionRect.width,selectionRect.height);
}
for (i = 0; i < bodyParts.length; i++)
{
bodyPartInfo = bodyParts[i];
joint = bodyPartInfo.joint as b2RevoluteJoint;
sp = joint.m_userData as Sprite;
if (sp)
{
if (sp.parent != m_sprite)
{
m_sprite.addChild(sp);
sp.name = i.toString();
sp.addEventListener(MouseEvent.MOUSE_DOWN, OnJointDrag);
}
if (selecting)
{
var bounds:Rectangle = sp.getBounds(m_sprite);
if (bounds.intersects(selectionRect))
{
sp.filters = [glow];
}
else
{
sp.filters = [];
}
}
else
sp.filters = [];
var center:b2Vec2 = joint.GetAnchor1();
center.Add(joint.GetAnchor2());
center.Multiply(0.5);
sp.x = center.x*scale;
sp.y = center.y * scale;
dir = (joint as b2RevoluteJoint).GetBody2().GetPosition().Copy();
dir.Subtract((joint as b2RevoluteJoint).GetAnchor2());
var ang:Number = Math.atan2(dir.y,dir.x)* (180 / Math.PI);
sp.rotation = ang;// (joint as b2RevoluteJoint).GetBody2().GetAngle() * (180 / Math.PI);
sp.alpha = 0.5 + (bodyPartInfo.state?0.5:0);
}
if (bodyParts[i].state)
{
(joint as b2RevoluteJoint).m_enableMotor = true;
var angleDif:Number = joint.GetJointAngle() - Number(bodyPartInfo.angle);///(180/Math.PI);
//trace("dif:"+angleDif);
if(angleDif!=0)
joint.SetMotorSpeed( -5 * angleDif);
}
else
{
(joint as b2RevoluteJoint).m_enableMotor = false;
}
}
}
public function bringDownRadian(n:Number):Number
{
var res:Number = n%(Math.PI*2);
return res;
}
public function OnStageDrag(e:Event):void
{
if (e.target == stage)
{
selecting = true;
selectionStartX = m_sprite.mouseX;
selectionStartY = m_sprite.mouseY;
}
}
public function OnMouseRelease(e:Event):void
{
if(dragedJoint)
{
var i:uint = int(dragedJoint.name);
var bodyPartInfo:Object = bodyParts[i];
var joint:b2RevoluteJoint = bodyPartInfo.joint as b2RevoluteJoint;
joint.SetMotorSpeed(0);
dragedJoint = null;
}
if (selecting)
{
selecting = false;
var selected:Array = [];
var activeCount:int = 0;
var unactiveCount:int = 0;
var selectionRect:Rectangle = new Rectangle(Math.min(m_sprite.mouseX, selectionStartX), Math.min(m_sprite.mouseY, selectionStartY), Math.abs(m_sprite.mouseX - selectionStartX), Math.abs(m_sprite.mouseY - selectionStartY));
for (i = 0; i < bodyParts.length; i++)
{
bodyPartInfo = bodyParts[i];
joint = bodyPartInfo.joint as b2RevoluteJoint;
var sp:Sprite = joint.m_userData as Sprite;
sp.filters = [];
var bounds:Rectangle = sp.getBounds(m_sprite);
if (bounds.intersects(selectionRect))
{
selected.push(bodyPartInfo);
if (bodyPartInfo.state)
activeCount++
else
unactiveCount++
}
}
var newState:Boolean = unactiveCount>activeCount;
for (i = 0; i < selected.length; i++)
{
selected[i].state = newState;
selected[i].angle = selected[i].joint.GetJointAngle();
}
}
}
public var mouseDownTime:Number;
public function OnJointDrag(e:Event):void
{
mouseDownTime = getTimer();
dragedJoint = e.currentTarget as Sprite;
var i:uint = int(dragedJoint.name);
var bodyPartInfo:Object = bodyParts[i];
var joint:b2RevoluteJoint = bodyPartInfo.joint as b2RevoluteJoint;
if (bodyPartInfo.state)
{
bodyPartInfo.state = false;
}
else
{
bodyPartInfo.state = true;
bodyPartInfo.angle = joint.GetJointAngle();
}
}
}
}
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Collision.Shapes.*;
import Box2D.Dynamics.Joints.*;
import Box2D.Dynamics.Contacts.*;
import Box2D.Common.*;
import Box2D.Common.Math.*;
import com.greensock.*;
import com.greensock.easing.*;
import flash.filters.DropShadowFilter;
import flash.geom.Rectangle;
import flash.utils.setTimeout;
import Main;
import flash.utils.getTimer
import flash.display.*;
class SandBox{
public var muscles:Array;
public var m_world:b2World;
public var m_bomb:b2Body;
public var m_mouseJoint:b2MouseJoint;
public var m_iterations:int = 10;
public var m_timeStep:Number = 1/30;
public var m_physScale:Number = 30;
// Sprite to draw in to
public var m_sprite:Sprite;
public var m_door1Motor:b2PrismaticJoint;
public static var CostumeColor:uint = 0xcccccc;
public var contactListener:ContactListener;
public var BodyParts:Array;
public var button:b2Body;
public static var _instance:SandBox;
public var gravity:b2Vec2;
public function SandBox(){
m_sprite = Main.m_sprite;
_instance = this;
var worldAABB:b2AABB = new b2AABB();
worldAABB.lowerBound.Set(-1000.0, -1000.0);
worldAABB.upperBound.Set(1000.0, 1000.0);
// Define the gravity vector
gravity = new b2Vec2(0.0, 10.0);
// Allow bodies to sleep
var doSleep:Boolean = true;
// Construct a world object
m_world = new b2World(worldAABB, gravity, doSleep);
// set debug draw
/*var dbgDraw:b2DebugDraw = new b2DebugDraw();
var dbgSprite:Sprite = new Sprite();
m_sprite.addChild(dbgSprite);
dbgDraw.m_sprite = m_sprite;
dbgDraw.m_drawScale = 30.0;
dbgDraw.m_fillAlpha = 1;
dbgDraw.m_lineThickness = 1.0;
dbgDraw.m_drawFlags = b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit;
m_world.SetDebugDraw(dbgDraw);*/
contactListener = new ContactListener();
m_world.SetContactListener(contactListener);
TweenLite.to(gravity, 1, { delay:1, x:0,y:0, ease:Back.easeOut } );
//var t:GTween = new GTween(gravity, 1, { }, { delay:1, ease:fl.motion.easing.Back.easeOut } );
//t.proxy.x = 0;
//t.proxy.y = 0;
setTimeout(SFX.powerDown.play, 1000, false);
// Create border of boxes
var bd:b2BodyDef;
var circ:b2CircleDef = new b2CircleDef();
var box:b2PolygonDef = new b2PolygonDef();
var jd:b2RevoluteJointDef = new b2RevoluteJointDef();
var friction:Number = 0.7;
var restitiution :Number = 0.3;
muscles = [];
BodyParts = [];
{
var startX:Number = 250;
var startY:Number = 200;
// BODIES
// Head
circ.radius = 12.5 / m_physScale;
circ.density = 1.0;
circ.friction = friction;
circ.restitution = restitiution;
//circ.filter.groupIndex = -1;
bd = new b2BodyDef();
bd.allowSleep = false;
bd.position.Set(startX / m_physScale, (startY+3) / m_physScale);
var head:b2Body = m_world.CreateBody(bd);
head.m_userData = CreateHelmet(12.5);
head.CreateShape(circ);
head.SetMassFromShapes();
BodyParts.push(head);
// Torso1
box.SetAsBox(20 / m_physScale, 10 / m_physScale);
box.density = 1.0;
box.friction = friction; box.restitution = 0.1;
//box.filter.groupIndex = -1;
bd = new b2BodyDef();
bd.position.Set(startX / m_physScale, (startY + 28) / m_physScale);
var torso1:b2Body = m_world.CreateBody(bd);
torso1.CreateShape(box);
torso1.SetMassFromShapes();
BodyParts.push(torso1);
torso1.m_userData = CreateBoxSprite(CostumeColor,20,10);
// Torso2
box.SetAsBox(10 / m_physScale, 10 / m_physScale);
bd = new b2BodyDef();
bd.position.Set(startX / m_physScale, (startY + 43) / m_physScale);
var torso2:b2Body = m_world.CreateBody(bd);
torso2.CreateShape(box);
torso2.SetMassFromShapes();
torso2.m_userData = CreateBoxSprite(CostumeColor, 10, 10);
BodyParts.push(torso2);
// Torso3
bd = new b2BodyDef();
box.SetAsBox(15 / m_physScale, 10 / m_physScale);
bd.position.Set(startX / m_physScale, (startY + 58) / m_physScale);
var torso3:b2Body = m_world.CreateBody(bd);
torso3.CreateShape(box);
torso3.SetMassFromShapes();
torso3.m_userData = CreateBoxSprite(CostumeColor, 15, 10);
BodyParts.push(torso3);
// UpperArm
box.SetAsBox(18 / m_physScale, 6.5 / m_physScale);
box.density = 1.0;
box.friction = friction; box.restitution = 0.1;
//box.filter.groupIndex = -1;
bd = new b2BodyDef();
// L
bd.position.Set((startX - 33) / m_physScale, (startY + 20) / m_physScale);
var upperArmL:b2Body = m_world.CreateBody(bd);
upperArmL.CreateShape(box);
upperArmL.SetMassFromShapes();
upperArmL.m_userData = CreateBoxSprite(CostumeColor, 18, 6.5);
BodyParts.push(upperArmL);
// R
bd.position.Set((startX + 33) / m_physScale, (startY + 20) / m_physScale);
var upperArmR:b2Body = m_world.CreateBody(bd);
upperArmR.CreateShape(box);
upperArmR.SetMassFromShapes();
upperArmR.m_userData = CreateBoxSprite(CostumeColor, 18, 6.5);
BodyParts.push(upperArmR);
// LowerArm
box.SetAsBox(18/ m_physScale, 6 / m_physScale);
box.density = 1.0;
box.friction = friction; box.restitution = 0.1;
//box.filter.groupIndex = -1;
bd = new b2BodyDef();
// L
bd.position.Set((startX - 61) / m_physScale, (startY + 20) / m_physScale);
var lowerArmL:b2Body = m_world.CreateBody(bd);
lowerArmL.CreateShape(box);
lowerArmL.SetMassFromShapes();
lowerArmL.m_userData = CreateBoxSprite(CostumeColor, 17, 6);
BodyParts.push(lowerArmL);
// R
bd.position.Set((startX + 61) / m_physScale, (startY + 20) / m_physScale);
var lowerArmR:b2Body = m_world.CreateBody(bd);
lowerArmR.CreateShape(box);
lowerArmR.SetMassFromShapes();
lowerArmR.m_userData = CreateBoxSprite(CostumeColor, 17, 6);
BodyParts.push(lowerArmR);
//palms
// UpperLeg
box.SetAsBox(8 / m_physScale, 24 / m_physScale);
box.density = 2.0;
box.friction = friction;
box.restitution = restitiution;
box.filter.groupIndex = -1;
bd = new b2BodyDef();
// L
bd.position.Set((startX - 8) / m_physScale, (startY + 86) / m_physScale);
var upperLegL:b2Body = m_world.CreateBody(bd);
upperLegL.CreateShape(box);
upperLegL.SetMassFromShapes();
upperLegL.m_userData = CreateBoxSprite(CostumeColor, 7.5, 22);
BodyParts.push(upperLegL);
// R
bd.position.Set((startX + 8) / m_physScale, (startY + 86) / m_physScale);
var upperLegR:b2Body = m_world.CreateBody(bd);
upperLegR.CreateShape(box);
upperLegR.SetMassFromShapes();
upperLegR.m_userData = CreateBoxSprite(CostumeColor, 7.5, 22);
BodyParts.push(upperLegR);
// LowerLeg
box.SetAsBox(7 / m_physScale, 26 / m_physScale);
box.density = 2.0;
box.friction = friction;
box.restitution = restitiution;
box.filter.groupIndex = 0;
bd = new b2BodyDef();
// L
bd.position.Set((startX - 7.5) / m_physScale, (startY + 127) / m_physScale);
var lowerLegL:b2Body = m_world.CreateBody(bd);
lowerLegL.CreateShape(box);
lowerLegL.SetMassFromShapes();
lowerLegL.m_userData = CreateBoxSprite(CostumeColor, 8, 26);
BodyParts.push(lowerLegL);
// R
bd.position.Set((startX + 7.5) / m_physScale, (startY + 127) / m_physScale);
var lowerLegR:b2Body = m_world.CreateBody(bd);
lowerLegR.CreateShape(box);
lowerLegR.SetMassFromShapes();
lowerLegR.m_userData = CreateBoxSprite(CostumeColor, 8, 26);
BodyParts.push(lowerLegR);
// JOINTS
jd.enableLimit = true;
jd.maxMotorTorque = 50.0;
jd.motorSpeed = 0.0;
jd.enableMotor = true;
// Head to shoulders
jd.lowerAngle = -40 / (180/Math.PI);
jd.upperAngle = 40 / (180/Math.PI);
jd.Initialize(torso1, head, new b2Vec2(startX / m_physScale, (startY + 15) / m_physScale));
var neck:b2Joint = m_world.CreateJoint(jd);;
muscles["neck"] = neck;
var jointPointSize:Number = 6;
neck.m_userData = CreateCircleSprite(0xFFFF00, jointPointSize,true,true);
// Upper arm to shoulders
// L
jd.maxMotorTorque = 300.0;
jd.lowerAngle = -85 / (180/Math.PI);
jd.upperAngle = 130 / (180/Math.PI);
jd.Initialize(torso1, upperArmL, new b2Vec2((startX - 18) / m_physScale, (startY + 20) / m_physScale));
muscles["LShoulder"] = m_world.CreateJoint(jd);
muscles["LShoulder"].m_userData = CreateCircleSprite(0xFFFF00,jointPointSize,true,true);
//jd.enableMotor = false;
// R
jd.lowerAngle = -130 / (180/Math.PI);
jd.upperAngle = 85 / (180/Math.PI);
jd.Initialize(torso1, upperArmR, new b2Vec2((startX + 18) / m_physScale, (startY + 20) / m_physScale));
muscles["RShoulder"] = m_world.CreateJoint(jd);
muscles["RShoulder"].m_userData = CreateCircleSprite(0xFFFF00, jointPointSize,true,true);
// Lower arm to upper arm
// L
jd.lowerAngle = -130 / (180/Math.PI);
jd.upperAngle = 130 / (180/Math.PI);
jd.Initialize(upperArmL, lowerArmL, new b2Vec2((startX - 45) / m_physScale, (startY + 20) / m_physScale));
muscles["LElbow"] = m_world.CreateJoint(jd);
muscles["LElbow"].m_userData = CreateCircleSprite(0xFFFF00,jointPointSize,true,true);
// R
jd.lowerAngle = -130 / (180/Math.PI);
jd.upperAngle = 130 / (180/Math.PI);
jd.Initialize(upperArmR, lowerArmR, new b2Vec2((startX + 45) / m_physScale, (startY + 20) / m_physScale));
muscles["RElbow"] = m_world.CreateJoint(jd);
muscles["RElbow"].m_userData = CreateCircleSprite(0xFFFF00, jointPointSize,true,true);
// Shoulders/stomach
jd.maxMotorTorque = 50.0;
jd.lowerAngle = -15 / (180/Math.PI);
jd.upperAngle = 15 / (180/Math.PI);
jd.Initialize(torso1, torso2, new b2Vec2(startX / m_physScale, (startY + 35) / m_physScale));
muscles["UPress"] = m_world.CreateJoint(jd);
muscles["UPress"].m_userData = CreateCircleSprite(0xFFFF00, jointPointSize,true,true);
// Stomach/hips
jd.Initialize(torso2, torso3, new b2Vec2(startX / m_physScale, (startY + 50) / m_physScale));
muscles["DPress"] = m_world.CreateJoint(jd);
muscles["DPress"].m_userData = CreateCircleSprite(0xFFFF00,jointPointSize,true,true);
// Torso to upper leg
// L
jd.maxMotorTorque = 400.0;
jd.lowerAngle = -25 / (180/Math.PI);
jd.upperAngle = 135 / (180/Math.PI);
jd.Initialize(torso3, upperLegL, new b2Vec2((startX - 8) / m_physScale, (startY + 72) / m_physScale));
muscles["LLeg"] = m_world.CreateJoint(jd);
muscles["LLeg"].m_userData = CreateCircleSprite(0xFFFF00, jointPointSize,true,true);
// R
jd.lowerAngle = -135 / (180/Math.PI);
jd.upperAngle = 25 / (180/Math.PI);
jd.Initialize(torso3, upperLegR, new b2Vec2((startX + 8) / m_physScale, (startY + 72) / m_physScale));
muscles["RLeg"] = m_world.CreateJoint(jd);
muscles["RLeg"].m_userData = CreateCircleSprite(0xFFFF00, jointPointSize,true,true);
// Upper leg to lower leg
// L
jd.lowerAngle = -115 / (180/Math.PI);
jd.upperAngle = 25 / (180/Math.PI);
jd.Initialize(upperLegL, lowerLegL, new b2Vec2((startX - 8) / m_physScale, (startY + 105) / m_physScale));
muscles["LKnee"] = m_world.CreateJoint(jd);
muscles["LKnee"].m_userData = CreateCircleSprite(0xFFFF00, jointPointSize,true,true);
// R
jd.lowerAngle = -25 / (180/Math.PI);
jd.upperAngle = 115 / (180/Math.PI);
jd.Initialize(upperLegR, lowerLegR, new b2Vec2((startX + 8) / m_physScale, (startY + 105) / m_physScale));
muscles["RKnee"] = m_world.CreateJoint(jd);
muscles["RKnee"].m_userData = CreateCircleSprite(0xFFFF00, jointPointSize,true,true);
}
var texture:BitmapData = Textures.getShipAlloy(100, 100);
CreateWall(0, 0, 50, 800, texture);
CreateWall(0, 0, 1000, 50, texture);
CreateWall(0, 800, 1050, 50, texture);
CreateWall(1000, 0, 50, 850, texture);
CreateWall(0, 400, 800, 30, texture);
CreateWall(370, 0, 30, 200, texture);
CreateWall(770, 200, 30, 200, texture);
var boxPileStart:Number = 100;
for (var i:uint = 0; i < 4; i++)
{
CreateBox(boxPileStart + i * 40, 370, 40, 40, 2,0.1, 0x220000);
}
for (i = 0; i < 3; i++)
{
CreateBox(boxPileStart + i * 40, 330, 40, 40, 2,0.1, 0x220000);
}
CreateBox(boxPileStart+60, 290, 40, 40, 2,0.1, 0x220000);
for (i = 0; i < 15; i++)
{
CreateBox(100+Math.random()*900, 100+Math.random()*700, Math.random()*50+50, 50+Math.random()*50,1,0.1, 0x440000).ApplyForce(new b2Vec2(Math.random()*5, Math.random()*5), new b2Vec2(Math.random()*5, Math.random()*5));
}
// button
circ = new b2CircleDef();
circ.radius = 12 / m_physScale;
circ.isSensor = true;
bd = new b2BodyDef();
bd.position.Set(200/m_physScale, 600 / m_physScale);
button = m_world.CreateBody(bd);
button.m_userData = CreateButton(12);
button.CreateShape(circ);
button.SetMassFromShapes();
}
public function CreateBox(x:Number, y:Number, width:Number, height:Number, density:uint = 1,restitution:Number=0.1, color:uint = 0):b2Body
{
var bd:b2BodyDef;
var box:b2PolygonDef = new b2PolygonDef();
var jd:b2RevoluteJointDef = new b2RevoluteJointDef();
var friction:Number = 0.7;
var restitiution :Number = 0.3;
bd = new b2BodyDef();
bd.allowSleep = false;
bd.position.Set((x+width*0.5) / m_physScale, (y+height*0.5) / m_physScale);
box.SetAsBox(width*0.5 / m_physScale, height*0.5 / m_physScale);
box.density = density;
box.friction = friction;
box.restitution = restitution;
var Box:b2Body = m_world.CreateBody(bd);
Box.CreateShape(box);
Box.SetMassFromShapes();
Box.m_userData = CreateCrateSprite( width*0.5, height*0.5,0xDDDDDD);
return Box;
}
public static function CreateCrateSprite(hWidth:Number, hHeight:Number,color:uint):Sprite
{
var sp:Sprite = new Sprite();
var g:Graphics = sp.graphics;
g.lineStyle(4,color,1,true);
g.beginBitmapFill(Textures.getMetalWireTexture(color),null,true,true);
g.drawRoundRect(-hWidth, -hHeight, hWidth * 2, hHeight * 2,13,13);
g.endFill();
sp.filters = [new DropShadowFilter(0, 45, 0, 1, 6, 6, 1.5, 1, true)]
sp.cacheAsBitmap = true;
return sp;
}
public function CreateWall(x:Number, y:Number, width:Number, height:Number, texture:BitmapData):void
{
var wallSd:b2PolygonDef = new b2PolygonDef();
var wallBd:b2BodyDef = new b2BodyDef();
var wallB:b2Body;
//wallSd.
wallBd.position.Set((x+width/2)/m_physScale, (y+height/2)/m_physScale);
wallSd.SetAsBox(width*0.5/m_physScale, height*0.5/m_physScale);
wallB = m_world.CreateBody(wallBd);
wallB.CreateShape(wallSd);
wallB.SetMassFromShapes();
wallB.m_userData = new Rectangle(x, y, width, height);
}
public static function CreateCircleSprite(color:uint, r:Number, interactive:Boolean = false, line:Boolean = false ):Sprite
{
var res:Sprite = new Sprite();
res.graphics.beginFill(color);
res.graphics.drawCircle( 0, 0, r);
res.graphics.endFill();
if (line)
{
res.graphics.lineStyle(1, 0);
res.graphics.moveTo(-r, 0);
res.graphics.lineTo(r, 0);
}
res.buttonMode = res.mouseEnabled = res.mouseChildren = interactive;
return res;
}
public static function CreateButton(r:Number):Sprite
{
var res:Sprite = new Sprite();
res.graphics.beginFill(0x8080dd);
res.graphics.drawCircle( 0, 0, r);
res.graphics.endFill();
res.graphics.beginFill(0xaa0000);
res.graphics.drawCircle(0, 0, -r * 0.8);
res.filters = [new DropShadowFilter()];
return res;
}
public static function CreateHelmet(r:Number):Sprite
{
var res:Sprite = new Sprite();
res.graphics.beginFill(CostumeColor);
res.graphics.drawCircle( 0, 0, r);
res.graphics.endFill();
res.graphics.beginFill(0x0000aa);
res.graphics.drawEllipse(-r*0.9, -r*0.7, r * 1.8, r * 1.6);
res.graphics.endFill();
res.graphics.beginFill(0xFFFFFF);
res.graphics.drawCircle( -r * 0.4, -r * 0.2, r * 0.3);
return res;
}
public static function CreateBoxSprite(color:uint, hWidth:Number, hHeight:Number,interactive:Boolean=false,texture:BitmapData=null):Sprite
{
var res:Sprite = new Sprite();
if (texture)
res.graphics.beginBitmapFill(texture);
else
res.graphics.beginFill(color);
res.graphics.drawRect( -hWidth, -hHeight, hWidth * 2, hHeight * 2);
res.buttonMode = res.mouseEnabled = res.mouseChildren = interactive;
res.graphics.endFill();
return res;
}
public function Update():void{
var physStart:uint = getTimer();
m_world.Step(m_timeStep, m_iterations);
}
}
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.filters.BevelFilter;
import flash.filters.BlurFilter;
import flash.geom.ColorTransform;
import flash.display.Graphics;
import flash.display.BlendMode;
import flash.geom.Matrix;
import flash.geom.Point;
class Textures
{
public static var bmd:BitmapData;
public static var sp:Sprite = new Sprite();
public static var blr:BlurFilter = new BlurFilter(2,2);
public static var sp2:Sprite = new Sprite();
public static var bmd2:BitmapData;
public static var clrt:ColorTransform = new ColorTransform(0.9,0.9,0.9);
public static var bvl:BevelFilter = new BevelFilter(4,45,0xE9D6CF,1,0x352726,1,4,4);
public static var m:Matrix = new Matrix();
public static function getStarSky(width:Number,height:Number):BitmapData
{
sp2.addChild(sp);
bmd = new BitmapData(width,height,false,0);
bmd2 = new BitmapData(bmd.width,bmd.height,true,0);
sp.graphics.clear();
var space:Number = bmd.width*bmd.height;
var g:Graphics = sp.graphics;
g.beginFill(0xFFFFFF);
var stars:Number = 10*space/10000;
for(var i:uint = 0;i<stars;i++)
{
var size:Number = Math.random()*2;
var xx:Number = Math.random()*(bmd.width-10)+5;
var yy:Number = Math.random()*(bmd.width-10)+5;
g.drawCircle(xx,yy,size);
}
sp.filters = [blr];
bmd.draw(sp2);
bmd2.fillRect(bmd2.rect,0);
bmd2.perlinNoise(500,500,3,Math.random()*int.MAX_VALUE,true,true,7);
bmd.draw(bmd2,null,null,BlendMode.MULTIPLY);
bmd2.colorTransform(bmd2.rect,clrt);
bmd.draw(bmd2,null,null,BlendMode.HARDLIGHT);
return bmd;
}
public static function getMetalWireTexture(color:uint):BitmapData
{
var g:Graphics = sp.graphics;
g.clear();
g.beginFill(color);
g.drawRect(-3,-3,23,21);
g.drawCircle(0,7,3);
g.drawCircle(8,7,3);
g.drawCircle(16,7,3);
g.drawCircle(4,0,3);
g.drawCircle(12,0,3);
g.drawCircle(4,14,3);
g.drawCircle(12, 14, 3);
m.tx = m.ty = 0;
m.b = m.c = 0;
m.a = m.d = 0.7;
sp.filters = [new BevelFilter(4,45,0xffffff,1,0,1,6,6)];
var bmd:BitmapData = new BitmapData(11,10,false,0);
bmd.draw(sp, m);
sp.filters = [];
m.a = m.d = 1;
return bmd;
}
public static function getShipAlloy(width:Number, height:Number, steps:Number = 3):BitmapData
{
bmd = new BitmapData(width,height,false,0);
bmd2 = new BitmapData(bmd.width*3, bmd.height*3, true, 0);
bmd2.fillRect(bmd2.rect,0);
bmd.fillRect(bmd.rect,0x605788);
for(var k:uint = 0;k<steps;k++){
sp.graphics.clear();
var g:Graphics = sp.graphics;
var rects:Number = 20;
g.clear();
for(var i:uint = 0;i<rects;i++)
{
g.beginFill(0x605788);
if(Math.random()<0.6)
g.lineStyle(1,0x7B738F);
else
g.lineStyle(0,0,0);
var w:Number = Math.random()*20+10;
var h:Number = Math.random()*20+10;
var xx:Number = Math.random()*(bmd.width+10-w)-5;
var yy:Number = Math.random()*(bmd.width+10-h)-5;
g.drawRect(xx,yy,w,h);
}
for(i=0;i<3;i++)
{
for(var j:uint=0;j<3;j++)
{
m.tx = bmd.width*i;
m.ty = bmd.height*j;
bmd2.draw(sp,m,null,BlendMode.HARDLIGHT);
}
}
//bmd2.applyFilter(bmd2,bmd2.rect,new Point(),bvl);
m.tx = -bmd.width;
m.ty = -bmd.height;
bmd.draw(bmd2,m);
}
return bmd;
}
}
import Box2D.Collision.b2ContactPoint;
import Box2D.Dynamics.b2ContactListener;
import Box2D.Dynamics.Contacts.b2ContactResult;
/**
* ...
* @author EduardRuzga www.wonderwhy-er.com
* Game made for LudumDare 21 compo
*/
class ContactListener extends b2ContactListener
{
public override function Add(point:b2ContactPoint) : void
{
// handle add point
//trace("contact");
var l:Number = point.velocity.Length();
var i1:Boolean = SandBox._instance.BodyParts.indexOf(point.shape1.m_body) > 0;
var i2:Boolean = SandBox._instance.BodyParts.indexOf(point.shape2.m_body) > 0;
if ( i1 || i2 )
{
if (point.shape1.m_body == SandBox._instance.button || point.shape2.m_body == SandBox._instance.button)
{
Main._instance.ButtonPress();
}
else
{
//trace(l);
if (l > 2 && (i1 != i2) )
{
SFX.bodySound.play(l/10);
}
}
}
else
{
if (l > 2)
SFX.crateSound.play(l/10);
}
}
public override function Persist(point:b2ContactPoint) : void
{
// handle persist point
}
public override function Remove(point:b2ContactPoint) : void
{
// handle remove point
}
public override function Result(point:b2ContactResult) : void
{
// handle results
}
}
class SFX
{
public static var bodySound:SfxrSynth;
public static var crateSound:SfxrSynth;
public static var powerUp:SfxrSynth;
public static var powerDown:SfxrSynth;
public static function init():void
{
bodySound = new SfxrSynth();
bodySound.params.setSettingsString("3,,0.1022,,0.23,0.09,,-0.6283,,0.4,0.31,0.02,,,0.02,,,,1,,,,,0.34");
bodySound.cacheSound();
crateSound = new SfxrSynth();
crateSound.params.setSettingsString("3,,0.1368,0.7009,0.16,0.0907,,0.1222,,,,,,,,,0.2074,-0.1865,1,,,,,0.17");
crateSound.cacheSound();
powerUp = new SfxrSynth();
powerUp.params.setSettingsString("0,0.52,0.27,,0.81,0.2,,0.089,,,,,,0.5889,,,,,1,,,,,0.24");
powerUp.cacheSound();
powerDown = new SfxrSynth();
powerDown.params.setSettingsString("0,0.09,0.1,,0.6,0.2,,0.089,,,,,,0.13,-0.48,,-0.04,,1,,,,-0.02,0.19");
powerDown.cacheSound();
}
}
import flash.display.Shape;
import flash.events.Event;
import flash.events.SampleDataEvent;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.utils.ByteArray;
import flash.utils.Endian;
import flash.utils.getTimer;
/**
* SfxrSynth
*
* Copyright 2010 Thomas Vian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author Thomas Vian
*/
class SfxrSynth
{
//--------------------------------------------------------------------------
//
// Sound Parameters
//
//--------------------------------------------------------------------------
private var _params:SfxrParams = new SfxrParams; // Params instance
private var _sound:Sound; // Sound instance used to play the sound
private var _channel:SoundChannel; // SoundChannel instance of playing Sound
private var _mutation:Boolean; // If the current sound playing or caching is a mutation
private var _cachedWave:ByteArray; // Cached wave data from a cacheSound() call
private var _cachingNormal:Boolean; // If the synth is caching a normal sound
private var _cachingMutation:int; // Current caching ID
private var _cachedMutation:ByteArray; // Current caching wave data for mutation
private var _cachedMutations:Vector.<ByteArray>; // Cached mutated wave data
private var _cachedMutationsNum:uint; // Number of cached mutations
private var _cachedMutationAmount:Number; // Amount to mutate during cache
private var _cachingAsync:Boolean; // If the synth is currently caching asynchronously
private var _cacheTimePerFrame:uint; // Maximum time allowed per frame to cache sound asynchronously
private var _cachedCallback:Function; // Function to call when finished caching asynchronously
private var _cacheTicker:Shape; // Shape used for enterFrame event
private var _waveData:ByteArray; // Full wave, read out in chuncks by the onSampleData method
private var _waveDataPos:uint; // Current position in the waveData
private var _waveDataLength:uint; // Number of bytes in the waveData
private var _waveDataBytes:uint; // Number of bytes to write to the soundcard
private var _original:SfxrParams; // Copied properties for mutation base
//--------------------------------------------------------------------------
//
// Synth Variables
//
//--------------------------------------------------------------------------
private var _finished:Boolean; // If the sound has finished
private var _masterVolume:Number; // masterVolume * masterVolume (for quick calculations)
private var _waveType:uint; // The type of wave to generate
private var _envelopeVolume:Number; // Current volume of the envelope
private var _envelopeStage:int; // Current stage of the envelope (attack, sustain, decay, end)
private var _envelopeTime:Number; // Current time through current enelope stage
private var _envelopeLength:Number; // Length of the current envelope stage
private var _envelopeLength0:Number; // Length of the attack stage
private var _envelopeLength1:Number; // Length of the sustain stage
private var _envelopeLength2:Number; // Length of the decay stage
private var _envelopeOverLength0:Number; // 1 / _envelopeLength0 (for quick calculations)
private var _envelopeOverLength1:Number; // 1 / _envelopeLength1 (for quick calculations)
private var _envelopeOverLength2:Number; // 1 / _envelopeLength2 (for quick calculations)
private var _envelopeFullLength:Number; // Full length of the volume envelop (and therefore sound)
private var _sustainPunch:Number; // The punch factor (louder at begining of sustain)
private var _phase:int; // Phase through the wave
private var _pos:Number; // Phase expresed as a Number from 0-1, used for fast sin approx
private var _period:Number; // Period of the wave
private var _periodTemp:Number; // Period modified by vibrato
private var _maxPeriod:Number; // Maximum period before sound stops (from minFrequency)
private var _slide:Number; // Note slide
private var _deltaSlide:Number; // Change in slide
private var _minFreqency:Number; // Minimum frequency before stopping
private var _vibratoPhase:Number; // Phase through the vibrato sine wave
private var _vibratoSpeed:Number; // Speed at which the vibrato phase moves
private var _vibratoAmplitude:Number; // Amount to change the period of the wave by at the peak of the vibrato wave
private var _changeAmount:Number; // Amount to change the note by
private var _changeTime:int; // Counter for the note change
private var _changeLimit:int; // Once the time reaches this limit, the note changes
private var _squareDuty:Number; // Offset of center switching point in the square wave
private var _dutySweep:Number; // Amount to change the duty by
private var _repeatTime:int; // Counter for the repeats
private var _repeatLimit:int; // Once the time reaches this limit, some of the variables are reset
private var _phaser:Boolean; // If the phaser is active
private var _phaserOffset:Number; // Phase offset for phaser effect
private var _phaserDeltaOffset:Number; // Change in phase offset
private var _phaserInt:int; // Integer phaser offset, for bit maths
private var _phaserPos:int; // Position through the phaser buffer
private var _phaserBuffer:Vector.<Number>; // Buffer of wave values used to create the out of phase second wave
private var _filters:Boolean; // If the filters are active
private var _lpFilterPos:Number; // Adjusted wave position after low-pass filter
private var _lpFilterOldPos:Number; // Previous low-pass wave position
private var _lpFilterDeltaPos:Number; // Change in low-pass wave position, as allowed by the cutoff and damping
private var _lpFilterCutoff:Number; // Cutoff multiplier which adjusts the amount the wave position can move
private var _lpFilterDeltaCutoff:Number; // Speed of the low-pass cutoff multiplier
private var _lpFilterDamping:Number; // Damping muliplier which restricts how fast the wave position can move
private var _lpFilterOn:Boolean; // If the low pass filter is active
private var _hpFilterPos:Number; // Adjusted wave position after high-pass filter
private var _hpFilterCutoff:Number; // Cutoff multiplier which adjusts the amount the wave position can move
private var _hpFilterDeltaCutoff:Number; // Speed of the high-pass cutoff multiplier
private var _noiseBuffer:Vector.<Number>; // Buffer of random values used to generate noise
private var _superSample:Number; // Actual sample writen to the wave
private var _sample:Number; // Sub-sample calculated 8 times per actual sample, averaged out to get the super sample
private var _sampleCount:uint; // Number of samples added to the buffer sample
private var _bufferSample:Number; // Another supersample used to create a 22050Hz wave
private var _volumeM:Number = 1;
public static var Mute:Boolean = false;
//--------------------------------------------------------------------------
//
// Getters / Setters
//
//--------------------------------------------------------------------------
/** The sound parameters */
public function get params():SfxrParams { return _params; }
public function set params(value:SfxrParams):void
{
_params = value;
_params.paramsDirty = true;
}
//--------------------------------------------------------------------------
//
// Sound Methods
//
//--------------------------------------------------------------------------
/**
* Plays the sound. If the parameters are dirty, synthesises sound as it plays, caching it for later.
* If they're not, plays from the cached sound.
* Won't play if caching asynchronously.
*/
public function play(volume:Number=1):void
{
if (Mute) return;
_volumeM = volume;
//trace(_params.getSettingsString());
if (_cachingAsync) return;
stop();
_mutation = false;
if (_params.paramsDirty || _cachingNormal || !_cachedWave)
{
// Needs to cache new data
_cachedWave = new ByteArray;
_cachingNormal = true;
_waveData = null;
reset(true);
}
else
{
// Play from cached data
_waveData = _cachedWave;
_waveData.position = 0;
_waveDataLength = _waveData.length;
_waveDataBytes = 24576;
_waveDataPos = 0;
}
if (!_sound) (_sound = new Sound()).addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
_channel = _sound.play();
}
/**
* Plays a mutation of the sound. If the parameters are dirty, synthesises sound as it plays, caching it for later.
* If they're not, plays from the cached sound.
* Won't play if caching asynchronously.
* @param mutationAmount Amount of mutation
* @param mutationsNum The number of mutations to cache before picking from them
*/
public function playMutated(mutationAmount:Number = 0.05, mutationsNum:uint = 15):void
{
stop();
if (_cachingAsync) return;
_mutation = true;
_cachedMutationsNum = mutationsNum;
if (_params.paramsDirty || !_cachedMutations)
{
// New set of mutations
_cachedMutations = new Vector.<ByteArray>();
_cachingMutation = 0;
}
if (_cachingMutation != -1)
{
// Continuing caching new mutations
_cachedMutation = new ByteArray;
_cachedMutations[_cachingMutation] = _cachedMutation;
_waveData = null;
_original = _params.clone();
_params.mutate(mutationAmount);
reset(true);
}
else
{
// Play from random cached mutation
_waveData = _cachedMutations[uint(_cachedMutations.length * Math.random())];
_waveData.position = 0;
_waveDataLength = _waveData.length;
_waveDataBytes = 24576;
_waveDataPos = 0;
}
if (!_sound) (_sound = new Sound()).addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
_channel = _sound.play();
}
/**
* Stops the currently playing sound
*/
public function stop():void
{
if(_channel)
{
_channel.stop();
_channel = null;
}
if(_original)
{
_params.copyFrom(_original);
_original = null;
}
}
/**
* If there is a cached sound to play, reads out of the data.
* If there isn't, synthesises new chunch of data, caching it as it goes.
* @param e SampleDataEvent to write data to
*/
private function onSampleData(e:SampleDataEvent):void
{
if(_waveData)
{
if(_waveDataPos + _waveDataBytes > _waveDataLength) _waveDataBytes = _waveDataLength - _waveDataPos;
if (_waveDataBytes > 0)
{
if(_volumeM==1)
e.data.writeBytes(_waveData, _waveDataPos, _waveDataBytes);
else
{
_waveData.position = _waveDataPos;
var floats:Number = _waveDataBytes / 4;
for (var i:uint = 0; i < floats; i++)
{
e.data.writeFloat(_waveData.readFloat() * _volumeM);
}
}
}
_waveDataPos += _waveDataBytes;
}
else
{
var length:uint;
var l:uint;
if (_mutation)
{
if (_original)
{
_waveDataPos = _cachedMutation.position;
if (synthWave(_cachedMutation, 3072, true))
{
_params.copyFrom(_original);
_original = null;
_cachingMutation++;
if ((length = _cachedMutation.length) < 24576)
{
// If the sound is smaller than the buffer length, add silence to allow it to play
_cachedMutation.position = length;
for(i = 0, l = 24576 - length; i < l; i++) _cachedMutation.writeFloat(0.0);
}
if (_cachingMutation >= _cachedMutationsNum)
{
_cachingMutation = -1;
}
}
_waveDataBytes = _cachedMutation.length - _waveDataPos;
e.data.writeBytes(_cachedMutation, _waveDataPos, _waveDataBytes);
}
}
else
{
if (_cachingNormal)
{
_waveDataPos = _cachedWave.position;
if (synthWave(_cachedWave, 3072, true))
{
if ((length = _cachedWave.length) < 24576)
{
// If the sound is smaller than the buffer length, add silence to allow it to play
_cachedWave.position = length;
for(i = 0, l = 24576 - length; i < l; i++) _cachedWave.writeFloat(0.0);
}
_cachingNormal = false;
}
_waveDataBytes = _cachedWave.length - _waveDataPos;
e.data.writeBytes(_cachedWave, _waveDataPos, _waveDataBytes);
}
}
}
}
//--------------------------------------------------------------------------
//
// Cached Sound Methods
//
//--------------------------------------------------------------------------
/**
* Cache the sound for speedy playback.
* If a callback is passed in, the caching will be done asynchronously, taking maxTimePerFrame milliseconds
* per frame to cache, them calling the callback when it's done.
* If not, the whole sound is cached imidiately - can freeze the player for a few seconds, especially in debug mode.
* @param callback Function to call when the caching is complete
* @param maxTimePerFrame Maximum time in milliseconds the caching will use per frame
*/
public function cacheSound(callback:Function = null, maxTimePerFrame:uint = 5):void
{
stop();
if (_cachingAsync) return;
reset(true);
_cachedWave = new ByteArray();
if (Boolean(callback))
{
_mutation = false;
_cachingNormal = true;
_cachingAsync = true;
_cacheTimePerFrame = maxTimePerFrame;
_cachedCallback = callback;
if (!_cacheTicker) _cacheTicker = new Shape;
_cacheTicker.addEventListener(Event.ENTER_FRAME, cacheSection);
}
else
{
_cachingNormal = false;
_cachingAsync = false;
synthWave(_cachedWave, _envelopeFullLength, true);
var length:uint = _cachedWave.length;
if(length < 24576)
{
// If the sound is smaller than the buffer length, add silence to allow it to play
_cachedWave.position = length;
for(var i:uint = 0, l:uint = 24576 - length; i < l; i++) _cachedWave.writeFloat(0.0);
}
}
}
/**
* Caches a series of mutations on the source sound.
* If a callback is passed in, the caching will be done asynchronously, taking maxTimePerFrame milliseconds
* per frame to cache, them calling the callback when it's done.
* If not, the whole sound is cached imidiately - can freeze the player for a few seconds, especially in debug mode.
* @param mutationsNum Number of mutations to cache
* @param mutationAmount Amount of mutation
* @param callback Function to call when the caching is complete
* @param maxTimePerFrame Maximum time in milliseconds the caching will use per frame
*/
public function cacheMutations(mutationsNum:uint, mutationAmount:Number = 0.05, callback:Function = null, maxTimePerFrame:uint = 5):void
{
stop();
if (_cachingAsync) return;
_cachedMutationsNum = mutationsNum;
_cachedMutations = new Vector.<ByteArray>();
if (Boolean(callback))
{
_mutation = true;
_cachingMutation = 0;
_cachedMutation = new ByteArray;
_cachedMutations[0] = _cachedMutation;
_cachedMutationAmount = mutationAmount;
_original = _params.clone();
_params.mutate(mutationAmount);
reset(true);
_cachingAsync = true;
_cacheTimePerFrame = maxTimePerFrame;
_cachedCallback = callback;
if (!_cacheTicker) _cacheTicker = new Shape;
_cacheTicker.addEventListener(Event.ENTER_FRAME, cacheSection);
}
else
{
var original:SfxrParams = _params.clone();
for(var i:uint = 0; i < _cachedMutationsNum; i++)
{
_params.mutate(mutationAmount);
cacheSound();
_cachedMutations[i] = _cachedWave;
_params.copyFrom(original);
}
_cachingMutation = -1;
}
}
/**
* Performs the asynchronous cache, working for up to _cacheTimePerFrame milliseconds per frame
* @param e enterFrame event
*/
private function cacheSection(e:Event):void
{
var cacheStartTime:uint = getTimer();
while (getTimer() - cacheStartTime < _cacheTimePerFrame)
{
if (_mutation)
{
_waveDataPos = _cachedMutation.position;
if (synthWave(_cachedMutation, 500, true))
{
_params.copyFrom(_original);
_params.mutate(_cachedMutationAmount);
reset(true);
_cachingMutation++;
_cachedMutation = new ByteArray;
_cachedMutations[_cachingMutation] = _cachedMutation;
if (_cachingMutation >= _cachedMutationsNum)
{
_cachingMutation = -1;
_cachingAsync = false;
_params.paramsDirty = false;
_cachedCallback();
_cachedCallback = null;
_cacheTicker.removeEventListener(Event.ENTER_FRAME, cacheSection);
return;
}
}
}
else
{
_waveDataPos = _cachedWave.position;
if (synthWave(_cachedWave, 500, true))
{
_cachingNormal = false;
_cachingAsync = false;
_cachedCallback();
_cachedCallback = null;
_cacheTicker.removeEventListener(Event.ENTER_FRAME, cacheSection);
return;
}
}
}
}
//--------------------------------------------------------------------------
//
// Synth Methods
//
//--------------------------------------------------------------------------
/**
* Resets the runing variables from the params
* Used once at the start (total reset) and for the repeat effect (partial reset)
* @param totalReset If the reset is total
*/
private function reset(totalReset:Boolean):void
{
// Shorter reference
var p:SfxrParams = _params;
_period = 100.0 / (p.startFrequency * p.startFrequency + 0.001);
_maxPeriod = 100.0 / (p.minFrequency * p.minFrequency + 0.001);
_slide = 1.0 - p.slide * p.slide * p.slide * 0.01;
_deltaSlide = -p.deltaSlide * p.deltaSlide * p.deltaSlide * 0.000001;
if (p.waveType == 0)
{
_squareDuty = 0.5 - p.squareDuty * 0.5;
_dutySweep = -p.dutySweep * 0.00005;
}
if (p.changeAmount > 0.0) _changeAmount = 1.0 - p.changeAmount * p.changeAmount * 0.9;
else _changeAmount = 1.0 + p.changeAmount * p.changeAmount * 10.0;
_changeTime = 0;
if(p.changeSpeed == 1.0) _changeLimit = 0;
else _changeLimit = (1.0 - p.changeSpeed) * (1.0 - p.changeSpeed) * 20000 + 32;
if(totalReset)
{
p.paramsDirty = false;
_masterVolume = p.masterVolume * p.masterVolume;
_waveType = p.waveType;
if (p.sustainTime < 0.01) p.sustainTime = 0.01;
var totalTime:Number = p.attackTime + p.sustainTime + p.decayTime;
if (totalTime < 0.18)
{
var multiplier:Number = 0.18 / totalTime;
p.attackTime *= multiplier;
p.sustainTime *= multiplier;
p.decayTime *= multiplier;
}
_sustainPunch = p.sustainPunch;
_phase = 0;
_minFreqency = p.minFrequency;
_filters = p.lpFilterCutoff != 1.0 || p.hpFilterCutoff != 0.0;
_lpFilterPos = 0.0;
_lpFilterDeltaPos = 0.0;
_lpFilterCutoff = p.lpFilterCutoff * p.lpFilterCutoff * p.lpFilterCutoff * 0.1;
_lpFilterDeltaCutoff = 1.0 + p.lpFilterCutoffSweep * 0.0001;
_lpFilterDamping = 5.0 / (1.0 + p.lpFilterResonance * p.lpFilterResonance * 20.0) * (0.01 + _lpFilterCutoff);
if (_lpFilterDamping > 0.8) _lpFilterDamping = 0.8;
_lpFilterDamping = 1.0 - _lpFilterDamping;
_lpFilterOn = p.lpFilterCutoff != 1.0;
_hpFilterPos = 0.0;
_hpFilterCutoff = p.hpFilterCutoff * p.hpFilterCutoff * 0.1;
_hpFilterDeltaCutoff = 1.0 + p.hpFilterCutoffSweep * 0.0003;
_vibratoPhase = 0.0;
_vibratoSpeed = p.vibratoSpeed * p.vibratoSpeed * 0.01;
_vibratoAmplitude = p.vibratoDepth * 0.5;
_envelopeVolume = 0.0;
_envelopeStage = 0;
_envelopeTime = 0;
_envelopeLength0 = p.attackTime * p.attackTime * 100000.0;
_envelopeLength1 = p.sustainTime * p.sustainTime * 100000.0;
_envelopeLength2 = p.decayTime * p.decayTime * 100000.0 + 10;
_envelopeLength = _envelopeLength0;
_envelopeFullLength = _envelopeLength0 + _envelopeLength1 + _envelopeLength2;
_envelopeOverLength0 = 1.0 / _envelopeLength0;
_envelopeOverLength1 = 1.0 / _envelopeLength1;
_envelopeOverLength2 = 1.0 / _envelopeLength2;
_phaser = p.phaserOffset != 0.0 || p.phaserSweep != 0.0;
_phaserOffset = p.phaserOffset * p.phaserOffset * 1020.0;
if(p.phaserOffset < 0.0) _phaserOffset = -_phaserOffset;
_phaserDeltaOffset = p.phaserSweep * p.phaserSweep * p.phaserSweep * 0.2;
_phaserPos = 0;
if(!_phaserBuffer) _phaserBuffer = new Vector.<Number>(1024, true);
if(!_noiseBuffer) _noiseBuffer = new Vector.<Number>(32, true);
for(var i:uint = 0; i < 1024; i++) _phaserBuffer[i] = 0.0;
for(i = 0; i < 32; i++) _noiseBuffer[i] = Math.random() * 2.0 - 1.0;
_repeatTime = 0;
if (p.repeatSpeed == 0.0) _repeatLimit = 0;
else _repeatLimit = int((1.0-p.repeatSpeed) * (1.0-p.repeatSpeed) * 20000) + 32;
}
}
/**
* Writes the wave to the supplied buffer ByteArray
* @param buffer A ByteArray to write the wave to
* @param waveData If the wave should be written for the waveData
* @return If the wave is finished
*/
private function synthWave(buffer:ByteArray, length:uint, waveData:Boolean = false, sampleRate:uint = 44100, bitDepth:uint = 16):Boolean
{
_finished = false;
_sampleCount = 0;
_bufferSample = 0.0;
for(var i:uint = 0; i < length; i++)
{
if (_finished) return true;
// Repeats every _repeatLimit times, partially resetting the sound parameters
if(_repeatLimit != 0)
{
if(++_repeatTime >= _repeatLimit)
{
_repeatTime = 0;
reset(false);
}
}
// If _changeLimit is reached, shifts the pitch
if(_changeLimit != 0)
{
if(++_changeTime >= _changeLimit)
{
_changeLimit = 0;
_period *= _changeAmount;
}
}
// Acccelerate and apply slide
_slide += _deltaSlide;
_period *= _slide;
// Checks for frequency getting too low, and stops the sound if a minFrequency was set
if(_period > _maxPeriod)
{
_period = _maxPeriod;
if(_minFreqency > 0.0) _finished = true;
}
_periodTemp = _period;
// Applies the vibrato effect
if(_vibratoAmplitude > 0.0)
{
_vibratoPhase += _vibratoSpeed;
_periodTemp = _period * (1.0 + Math.sin(_vibratoPhase) * _vibratoAmplitude);
}
_periodTemp = int(_periodTemp);
if(_periodTemp < 8) _periodTemp = 8;
// Sweeps the square duty
if (_waveType == 0)
{
_squareDuty += _dutySweep;
if(_squareDuty < 0.0) _squareDuty = 0.0;
else if (_squareDuty > 0.5) _squareDuty = 0.5;
}
// Moves through the different stages of the volume envelope
if(++_envelopeTime > _envelopeLength)
{
_envelopeTime = 0;
switch(++_envelopeStage)
{
case 1: _envelopeLength = _envelopeLength1; break;
case 2: _envelopeLength = _envelopeLength2; break;
}
}
// Sets the volume based on the position in the envelope
switch(_envelopeStage)
{
case 0: _envelopeVolume = _envelopeTime * _envelopeOverLength0; break;
case 1: _envelopeVolume = 1.0 + (1.0 - _envelopeTime * _envelopeOverLength1) * 2.0 * _sustainPunch; break;
case 2: _envelopeVolume = 1.0 - _envelopeTime * _envelopeOverLength2; break;
case 3: _envelopeVolume = 0.0; _finished = true; break;
}
// Moves the phaser offset
if (_phaser)
{
_phaserOffset += _phaserDeltaOffset;
_phaserInt = int(_phaserOffset);
if(_phaserInt < 0) _phaserInt = -_phaserInt;
else if (_phaserInt > 1023) _phaserInt = 1023;
}
// Moves the high-pass filter cutoff
if(_filters && _hpFilterDeltaCutoff != 0.0)
{
_hpFilterCutoff *= _hpFilterDeltaCutoff;
if(_hpFilterCutoff < 0.00001) _hpFilterCutoff = 0.00001;
else if(_hpFilterCutoff > 0.1) _hpFilterCutoff = 0.1;
}
_superSample = 0.0;
for(var j:int = 0; j < 8; j++)
{
// Cycles through the period
_phase++;
if(_phase >= _periodTemp)
{
_phase = _phase - _periodTemp;
// Generates new random noise for this period
if(_waveType == 3)
{
for(var n:uint = 0; n < 32; n++) _noiseBuffer[n] = Math.random() * 2.0 - 1.0;
}
}
// Gets the sample from the oscillator
switch(_waveType)
{
case 0: // Square wave
{
_sample = ((_phase / _periodTemp) < _squareDuty) ? 0.5 : -0.5;
break;
}
case 1: // Saw wave
{
_sample = 1.0 - (_phase / _periodTemp) * 2.0;
break;
}
case 2: // Sine wave (fast and accurate approx)
{
_pos = _phase / _periodTemp;
_pos = _pos > 0.5 ? (_pos - 1.0) * 6.28318531 : _pos * 6.28318531;
_sample = _pos < 0 ? 1.27323954 * _pos + .405284735 * _pos * _pos : 1.27323954 * _pos - 0.405284735 * _pos * _pos;
_sample = _sample < 0 ? .225 * (_sample *-_sample - _sample) + _sample : .225 * (_sample * _sample - _sample) + _sample;
break;
}
case 3: // Noise
{
_sample = _noiseBuffer[Math.min(uint(_phase * 32 / int(_periodTemp)),31)];
break;
}
}
// Applies the low and high pass filters
if (_filters)
{
_lpFilterOldPos = _lpFilterPos;
_lpFilterCutoff *= _lpFilterDeltaCutoff;
if(_lpFilterCutoff < 0.0) _lpFilterCutoff = 0.0;
else if(_lpFilterCutoff > 0.1) _lpFilterCutoff = 0.1;
if(_lpFilterOn)
{
_lpFilterDeltaPos += (_sample - _lpFilterPos) * _lpFilterCutoff;
_lpFilterDeltaPos *= _lpFilterDamping;
}
else
{
_lpFilterPos = _sample;
_lpFilterDeltaPos = 0.0;
}
_lpFilterPos += _lpFilterDeltaPos;
_hpFilterPos += _lpFilterPos - _lpFilterOldPos;
_hpFilterPos *= 1.0 - _hpFilterCutoff;
_sample = _hpFilterPos;
}
// Applies the phaser effect
if (_phaser)
{
_phaserBuffer[_phaserPos&1023] = _sample;
_sample += _phaserBuffer[(_phaserPos - _phaserInt + 1024) & 1023];
_phaserPos = (_phaserPos + 1) & 1023;
}
_superSample += _sample;
}
// Averages out the super samples and applies volumes
_superSample = _masterVolume * _envelopeVolume * _superSample * 0.125;
// Clipping if too loud
if(_superSample > 1.0) _superSample = 1.0;
else if(_superSample < -1.0) _superSample = -1.0;
if(waveData)
{
// Writes same value to left and right channels
buffer.writeFloat(_superSample);
buffer.writeFloat(_superSample);
}
else
{
_bufferSample += _superSample;
_sampleCount++;
// Writes mono wave data to the .wav format
if(sampleRate == 44100 || _sampleCount == 2)
{
_bufferSample /= _sampleCount;
_sampleCount = 0;
if(bitDepth == 16) buffer.writeShort(int(32000.0 * _bufferSample));
else buffer.writeByte(_bufferSample * 127 + 128);
_bufferSample = 0.0;
}
}
}
return false;
}
//--------------------------------------------------------------------------
//
// .wav File Methods
//
//--------------------------------------------------------------------------
/**
* Returns a ByteArray of the wave in the form of a .wav file, ready to be saved out
* @param sampleRate Sample rate to generate the .wav at
* @param bitDepth Bit depth to generate the .wav at
* @return Wave in a .wav file
*/
public function getWavFile(sampleRate:uint = 44100, bitDepth:uint = 16):ByteArray
{
stop();
reset(true);
if (sampleRate != 44100) sampleRate = 22050;
if (bitDepth != 16) bitDepth = 8;
var soundLength:uint = _envelopeFullLength;
if (bitDepth == 16) soundLength *= 2;
if (sampleRate == 22050) soundLength /= 2;
var filesize:int = 36 + soundLength;
var blockAlign:int = bitDepth / 8;
var bytesPerSec:int = sampleRate * blockAlign;
var wav:ByteArray = new ByteArray();
// Header
wav.endian = Endian.BIG_ENDIAN;
wav.writeUnsignedInt(0x52494646); // Chunk ID "RIFF"
wav.endian = Endian.LITTLE_ENDIAN;
wav.writeUnsignedInt(filesize); // Chunck Data Size
wav.endian = Endian.BIG_ENDIAN;
wav.writeUnsignedInt(0x57415645); // RIFF Type "WAVE"
// Format Chunk
wav.endian = Endian.BIG_ENDIAN;
wav.writeUnsignedInt(0x666D7420); // Chunk ID "fmt "
wav.endian = Endian.LITTLE_ENDIAN;
wav.writeUnsignedInt(16); // Chunk Data Size
wav.writeShort(1); // Compression Code PCM
wav.writeShort(1); // Number of channels
wav.writeUnsignedInt(sampleRate); // Sample rate
wav.writeUnsignedInt(bytesPerSec); // Average bytes per second
wav.writeShort(blockAlign); // Block align
wav.writeShort(bitDepth); // Significant bits per sample
// Data Chunk
wav.endian = Endian.BIG_ENDIAN;
wav.writeUnsignedInt(0x64617461); // Chunk ID "data"
wav.endian = Endian.LITTLE_ENDIAN;
wav.writeUnsignedInt(soundLength); // Chunk Data Size
synthWave(wav, _envelopeFullLength, false, sampleRate, bitDepth);
wav.position = 0;
return wav;
}
}
/**
* SfxrParams
*
* Copyright 2010 Thomas Vian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author Thomas Vian
*/
class SfxrParams
{
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
/** If the parameters have been changed since last time (shouldn't used cached sound) */
public var paramsDirty:Boolean;
private var _waveType :uint = 0; // Shape of the wave (0:square, 1:saw, 2:sin or 3:noise)
private var _masterVolume :Number = 0.5; // Overall volume of the sound (0 to 1)
private var _attackTime :Number = 0.0; // Length of the volume envelope attack (0 to 1)
private var _sustainTime :Number = 0.0; // Length of the volume envelope sustain (0 to 1)
private var _sustainPunch :Number = 0.0; // Tilts the sustain envelope for more 'pop' (0 to 1)
private var _decayTime :Number = 0.0; // Length of the volume envelope decay (yes, I know it's called release) (0 to 1)
private var _startFrequency :Number = 0.0; // Base note of the sound (0 to 1)
private var _minFrequency :Number = 0.0; // If sliding, the sound will stop at this frequency, to prevent really low notes (0 to 1)
private var _slide :Number = 0.0; // Slides the note up or down (-1 to 1)
private var _deltaSlide :Number = 0.0; // Accelerates the slide (-1 to 1)
private var _vibratoDepth :Number = 0.0; // Strength of the vibrato effect (0 to 1)
private var _vibratoSpeed :Number = 0.0; // Speed of the vibrato effect (i.e. frequency) (0 to 1)
private var _changeAmount :Number = 0.0; // Shift in note, either up or down (-1 to 1)
private var _changeSpeed :Number = 0.0; // How fast the note shift happens (only happens once) (0 to 1)
private var _squareDuty :Number = 0.0; // Controls the ratio between the up and down states of the square wave, changing the tibre (0 to 1)
private var _dutySweep :Number = 0.0; // Sweeps the duty up or down (-1 to 1)
private var _repeatSpeed :Number = 0.0; // Speed of the note repeating - certain variables are reset each time (0 to 1)
private var _phaserOffset :Number = 0.0; // Offsets a second copy of the wave by a small phase, changing the tibre (-1 to 1)
private var _phaserSweep :Number = 0.0; // Sweeps the phase up or down (-1 to 1)
private var _lpFilterCutoff :Number = 0.0; // Frequency at which the low-pass filter starts attenuating higher frequencies (0 to 1)
private var _lpFilterCutoffSweep:Number = 0.0; // Sweeps the low-pass cutoff up or down (-1 to 1)
private var _lpFilterResonance :Number = 0.0; // Changes the attenuation rate for the low-pass filter, changing the timbre (0 to 1)
private var _hpFilterCutoff :Number = 0.0; // Frequency at which the high-pass filter starts attenuating lower frequencies (0 to 1)
private var _hpFilterCutoffSweep:Number = 0.0; // Sweeps the high-pass cutoff up or down (-1 to 1)
//--------------------------------------------------------------------------
//
// Getters / Setters
//
//--------------------------------------------------------------------------
/** Shape of the wave (0:square, 1:saw, 2:sin or 3:noise) */
public function get waveType():uint { return _waveType; }
public function set waveType(value:uint):void { _waveType = value > 3 ? 0 : value; paramsDirty = true; }
/** Overall volume of the sound (0 to 1) */
public function get masterVolume():Number { return _masterVolume; }
public function set masterVolume(value:Number):void { _masterVolume = clamp1(value); paramsDirty = true; }
/** Length of the volume envelope attack (0 to 1) */
public function get attackTime():Number { return _attackTime; }
public function set attackTime(value:Number):void { _attackTime = clamp1(value); paramsDirty = true; }
/** Length of the volume envelope sustain (0 to 1) */
public function get sustainTime():Number { return _sustainTime; }
public function set sustainTime(value:Number):void { _sustainTime = clamp1(value); paramsDirty = true; }
/** Tilts the sustain envelope for more 'pop' (0 to 1) */
public function get sustainPunch():Number { return _sustainPunch; }
public function set sustainPunch(value:Number):void { _sustainPunch = clamp1(value); paramsDirty = true; }
/** Length of the volume envelope decay (yes, I know it's called release) (0 to 1) */
public function get decayTime():Number { return _decayTime; }
public function set decayTime(value:Number):void { _decayTime = clamp1(value); paramsDirty = true; }
/** Base note of the sound (0 to 1) */
public function get startFrequency():Number { return _startFrequency; }
public function set startFrequency(value:Number):void { _startFrequency = clamp1(value); paramsDirty = true; }
/** If sliding, the sound will stop at this frequency, to prevent really low notes (0 to 1) */
public function get minFrequency():Number { return _minFrequency; }
public function set minFrequency(value:Number):void { _minFrequency = clamp1(value); paramsDirty = true; }
/** Slides the note up or down (-1 to 1) */
public function get slide():Number { return _slide; }
public function set slide(value:Number):void { _slide = clamp2(value); paramsDirty = true; }
/** Accelerates the slide (-1 to 1) */
public function get deltaSlide():Number { return _deltaSlide; }
public function set deltaSlide(value:Number):void { _deltaSlide = clamp2(value); paramsDirty = true; }
/** Strength of the vibrato effect (0 to 1) */
public function get vibratoDepth():Number { return _vibratoDepth; }
public function set vibratoDepth(value:Number):void { _vibratoDepth = clamp1(value); paramsDirty = true; }
/** Speed of the vibrato effect (i.e. frequency) (0 to 1) */
public function get vibratoSpeed():Number { return _vibratoSpeed; }
public function set vibratoSpeed(value:Number):void { _vibratoSpeed = clamp1(value); paramsDirty = true; }
/** Shift in note, either up or down (-1 to 1) */
public function get changeAmount():Number { return _changeAmount; }
public function set changeAmount(value:Number):void { _changeAmount = clamp2(value); paramsDirty = true; }
/** How fast the note shift happens (only happens once) (0 to 1) */
public function get changeSpeed():Number { return _changeSpeed; }
public function set changeSpeed(value:Number):void { _changeSpeed = clamp1(value); paramsDirty = true; }
/** Controls the ratio between the up and down states of the square wave, changing the tibre (0 to 1) */
public function get squareDuty():Number { return _squareDuty; }
public function set squareDuty(value:Number):void { _squareDuty = clamp1(value); paramsDirty = true; }
/** Sweeps the duty up or down (-1 to 1) */
public function get dutySweep():Number { return _dutySweep; }
public function set dutySweep(value:Number):void { _dutySweep = clamp2(value); paramsDirty = true; }
/** Speed of the note repeating - certain variables are reset each time (0 to 1) */
public function get repeatSpeed():Number { return _repeatSpeed; }
public function set repeatSpeed(value:Number):void { _repeatSpeed = clamp1(value); paramsDirty = true; }
/** Offsets a second copy of the wave by a small phase, changing the tibre (-1 to 1) */
public function get phaserOffset():Number { return _phaserOffset; }
public function set phaserOffset(value:Number):void { _phaserOffset = clamp2(value); paramsDirty = true; }
/** Sweeps the phase up or down (-1 to 1) */
public function get phaserSweep():Number { return _phaserSweep; }
public function set phaserSweep(value:Number):void { _phaserSweep = clamp2(value); paramsDirty = true; }
/** Frequency at which the low-pass filter starts attenuating higher frequencies (0 to 1) */
public function get lpFilterCutoff():Number { return _lpFilterCutoff; }
public function set lpFilterCutoff(value:Number):void { _lpFilterCutoff = clamp1(value); paramsDirty = true; }
/** Sweeps the low-pass cutoff up or down (-1 to 1) */
public function get lpFilterCutoffSweep():Number { return _lpFilterCutoffSweep; }
public function set lpFilterCutoffSweep(value:Number):void { _lpFilterCutoffSweep = clamp2(value); paramsDirty = true; }
/** Changes the attenuation rate for the low-pass filter, changing the timbre (0 to 1) */
public function get lpFilterResonance():Number { return _lpFilterResonance; }
public function set lpFilterResonance(value:Number):void { _lpFilterResonance = clamp1(value); paramsDirty = true; }
/** Frequency at which the high-pass filter starts attenuating lower frequencies (0 to 1) */
public function get hpFilterCutoff():Number { return _hpFilterCutoff; }
public function set hpFilterCutoff(value:Number):void { _hpFilterCutoff = clamp1(value); paramsDirty = true; }
/** Sweeps the high-pass cutoff up or down (-1 to 1) */
public function get hpFilterCutoffSweep():Number { return _hpFilterCutoffSweep; }
public function set hpFilterCutoffSweep(value:Number):void { _hpFilterCutoffSweep = clamp2(value); paramsDirty = true; }
//--------------------------------------------------------------------------
//
// Generator Methods
//
//--------------------------------------------------------------------------
/**
* Sets the parameters to generate a pickup/coin sound
*/
public function generatePickupCoin():void
{
resetParams();
_startFrequency = 0.4 + Math.random() * 0.5;
_sustainTime = Math.random() * 0.1;
_decayTime = 0.1 + Math.random() * 0.4;
_sustainPunch = 0.3 + Math.random() * 0.3;
if(Math.random() < 0.5)
{
_changeSpeed = 0.5 + Math.random() * 0.2;
_changeAmount = 0.2 + Math.random() * 0.4;
}
}
/**
* Sets the parameters to generate a laser/shoot sound
*/
public function generateLaserShoot():void
{
resetParams();
_waveType = uint(Math.random() * 3);
if(_waveType == 2 && Math.random() < 0.5) _waveType = uint(Math.random() * 2);
_startFrequency = 0.5 + Math.random() * 0.5;
_minFrequency = _startFrequency - 0.2 - Math.random() * 0.6;
if(_minFrequency < 0.2) _minFrequency = 0.2;
_slide = -0.15 - Math.random() * 0.2;
if(Math.random() < 0.33)
{
_startFrequency = 0.3 + Math.random() * 0.6;
_minFrequency = Math.random() * 0.1;
_slide = -0.35 - Math.random() * 0.3;
}
if(Math.random() < 0.5)
{
_squareDuty = Math.random() * 0.5;
_dutySweep = Math.random() * 0.2;
}
else
{
_squareDuty = 0.4 + Math.random() * 0.5;
_dutySweep =- Math.random() * 0.7;
}
_sustainTime = 0.1 + Math.random() * 0.2;
_decayTime = Math.random() * 0.4;
if(Math.random() < 0.5) _sustainPunch = Math.random() * 0.3;
if(Math.random() < 0.33)
{
_phaserOffset = Math.random() * 0.2;
_phaserSweep = -Math.random() * 0.2;
}
if(Math.random() < 0.5) _hpFilterCutoff = Math.random() * 0.3;
}
/**
* Sets the parameters to generate an explosion sound
*/
public function generateExplosion():void
{
resetParams();
_waveType = 3;
if(Math.random() < 0.5)
{
_startFrequency = 0.1 + Math.random() * 0.4;
_slide = -0.1 + Math.random() * 0.4;
}
else
{
_startFrequency = 0.2 + Math.random() * 0.7;
_slide = -0.2 - Math.random() * 0.2;
}
_startFrequency *= _startFrequency;
if(Math.random() < 0.2) _slide = 0.0;
if(Math.random() < 0.33) _repeatSpeed = 0.3 + Math.random() * 0.5;
_sustainTime = 0.1 + Math.random() * 0.3;
_decayTime = Math.random() * 0.5;
_sustainPunch = 0.2 + Math.random() * 0.6;
if(Math.random() < 0.5)
{
_phaserOffset = -0.3 + Math.random() * 0.9;
_phaserSweep = -Math.random() * 0.3;
}
if(Math.random() < 0.33)
{
_changeSpeed = 0.6 + Math.random() * 0.3;
_changeAmount = 0.8 - Math.random() * 1.6;
}
}
/**
* Sets the parameters to generate a powerup sound
*/
public function generatePowerup():void
{
resetParams();
if(Math.random() < 0.5) _waveType = 1;
else _squareDuty = Math.random() * 0.6;
if(Math.random() < 0.5)
{
_startFrequency = 0.2 + Math.random() * 0.3;
_slide = 0.1 + Math.random() * 0.4;
_repeatSpeed = 0.4 + Math.random() * 0.4;
}
else
{
_startFrequency = 0.2 + Math.random() * 0.3;
_slide = 0.05 + Math.random() * 0.2;
if(Math.random() < 0.5)
{
_vibratoDepth = Math.random() * 0.7;
_vibratoSpeed = Math.random() * 0.6;
}
}
_sustainTime = Math.random() * 0.4;
_decayTime = 0.1 + Math.random() * 0.4;
}
/**
* Sets the parameters to generate a hit/hurt sound
*/
public function generateHitHurt():void
{
resetParams();
_waveType = uint(Math.random() * 3);
if(_waveType == 2) _waveType = 3;
else if(_waveType == 0) _squareDuty = Math.random() * 0.6;
_startFrequency = 0.2 + Math.random() * 0.6;
_slide = -0.3 - Math.random() * 0.4;
_sustainTime = Math.random() * 0.1;
_decayTime = 0.1 + Math.random() * 0.2;
if(Math.random() < 0.5) _hpFilterCutoff = Math.random() * 0.3;
}
/**
* Sets the parameters to generate a jump sound
*/
public function generateJump():void
{
resetParams();
_waveType = 0;
_squareDuty = Math.random() * 0.6;
_startFrequency = 0.3 + Math.random() * 0.3;
_slide = 0.1 + Math.random() * 0.2;
_sustainTime = 0.1 + Math.random() * 0.3;
_decayTime = 0.1 + Math.random() * 0.2;
if(Math.random() < 0.5) _hpFilterCutoff = Math.random() * 0.3;
if(Math.random() < 0.5) _lpFilterCutoff = 1.0 - Math.random() * 0.6;
}
/**
* Sets the parameters to generate a blip/select sound
*/
public function generateBlipSelect():void
{
resetParams();
_waveType = uint(Math.random() * 2);
if(_waveType == 0) _squareDuty = Math.random() * 0.6;
_startFrequency = 0.2 + Math.random() * 0.4;
_sustainTime = 0.1 + Math.random() * 0.1;
_decayTime = Math.random() * 0.2;
_hpFilterCutoff = 0.1;
}
/**
* Resets the parameters, used at the start of each generate function
*/
protected function resetParams():void
{
paramsDirty = true;
_waveType = 0;
_startFrequency = 0.3;
_minFrequency = 0.0;
_slide = 0.0;
_deltaSlide = 0.0;
_squareDuty = 0.0;
_dutySweep = 0.0;
_vibratoDepth = 0.0;
_vibratoSpeed = 0.0;
_attackTime = 0.0;
_sustainTime = 0.3;
_decayTime = 0.4;
_sustainPunch = 0.0;
_lpFilterResonance = 0.0;
_lpFilterCutoff = 1.0;
_lpFilterCutoffSweep = 0.0;
_hpFilterCutoff = 0.0;
_hpFilterCutoffSweep = 0.0;
_phaserOffset = 0.0;
_phaserSweep = 0.0;
_repeatSpeed = 0.0;
_changeSpeed = 0.0;
_changeAmount = 0.0;
}
//--------------------------------------------------------------------------
//
// Randomize Methods
//
//--------------------------------------------------------------------------
/**
* Randomly adjusts the parameters ever so slightly
*/
public function mutate(mutation:Number = 0.05):void
{
if (Math.random() < 0.5) startFrequency += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) minFrequency += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) slide += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) deltaSlide += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) squareDuty += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) dutySweep += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) vibratoDepth += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) vibratoSpeed += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) attackTime += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) sustainTime += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) decayTime += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) sustainPunch += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) lpFilterCutoff += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) lpFilterCutoffSweep += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) lpFilterResonance += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) hpFilterCutoff += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) hpFilterCutoffSweep += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) phaserOffset += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) phaserSweep += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) repeatSpeed += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) changeSpeed += Math.random() * mutation*2 - mutation;
if (Math.random() < 0.5) changeAmount += Math.random() * mutation*2 - mutation;
}
/**
* Sets all parameters to random values
*/
public function randomize():void
{
paramsDirty = true;
_waveType = uint(Math.random() * 4);
_attackTime = pow(Math.random()*2-1, 4);
_sustainTime = pow(Math.random()*2-1, 2);
_sustainPunch = pow(Math.random()*0.8, 2);
_decayTime = Math.random();
_startFrequency = (Math.random() < 0.5) ? pow(Math.random()*2-1, 2) : (pow(Math.random() * 0.5, 3) + 0.5);
_minFrequency = 0.0;
_slide = pow(Math.random()*2-1, 5);
_deltaSlide = pow(Math.random()*2-1, 3);
_vibratoDepth = pow(Math.random()*2-1, 3);
_vibratoSpeed = Math.random()*2-1;
_changeAmount = Math.random()*2-1;
_changeSpeed = Math.random()*2-1;
_squareDuty = Math.random()*2-1;
_dutySweep = pow(Math.random()*2-1, 3);
_repeatSpeed = Math.random()*2-1;
_phaserOffset = pow(Math.random()*2-1, 3);
_phaserSweep = pow(Math.random()*2-1, 3);
_lpFilterCutoff = 1 - pow(Math.random(), 3);
_lpFilterCutoffSweep = pow(Math.random()*2-1, 3);
_lpFilterResonance = Math.random()*2-1;
_hpFilterCutoff = pow(Math.random(), 5);
_hpFilterCutoffSweep = pow(Math.random()*2-1, 5);
if(_attackTime + _sustainTime + _decayTime < 0.2)
{
_sustainTime = 0.2 + Math.random() * 0.3;
_decayTime = 0.2 + Math.random() * 0.3;
}
if((_startFrequency > 0.7 && _slide > 0.2) || (_startFrequency < 0.2 && _slide < -0.05))
{
_slide = -_slide;
}
if(_lpFilterCutoff < 0.1 && _lpFilterCutoffSweep < -0.05)
{
_lpFilterCutoffSweep = -_lpFilterCutoffSweep;
}
}
//--------------------------------------------------------------------------
//
// Settings String Methods
//
//--------------------------------------------------------------------------
/**
* Returns a string representation of the parameters for copy/paste sharing
* @return A comma-delimited list of parameter values
*/
public function getSettingsString():String
{
var string:String = String(waveType);
string += "," + to4DP(_attackTime) + "," + to4DP(_sustainTime)
+ "," + to4DP(_sustainPunch) + "," + to4DP(_decayTime)
+ "," + to4DP(_startFrequency) + "," + to4DP(_minFrequency)
+ "," + to4DP(_slide) + "," + to4DP(_deltaSlide)
+ "," + to4DP(_vibratoDepth) + "," + to4DP(_vibratoSpeed)
+ "," + to4DP(_changeAmount) + "," + to4DP(_changeSpeed)
+ "," + to4DP(_squareDuty) + "," + to4DP(_dutySweep)
+ "," + to4DP(_repeatSpeed) + "," + to4DP(_phaserOffset)
+ "," + to4DP(_phaserSweep) + "," + to4DP(_lpFilterCutoff)
+ "," + to4DP(_lpFilterCutoffSweep) + "," + to4DP(_lpFilterResonance)
+ "," + to4DP(_hpFilterCutoff)+ "," + to4DP(_hpFilterCutoffSweep)
+ "," + to4DP(_masterVolume);
return string;
}
/**
* Parses a settings string into the parameters
* @param string Settings string to parse
* @return If the string successfully parsed
*/
public function setSettingsString(string:String):Boolean
{
var values:Array = string.split(",");
if (values.length != 24) return false;
waveType = uint(values[0]) || 0;
attackTime = Number(values[1]) || 0;
sustainTime = Number(values[2]) || 0;
sustainPunch = Number(values[3]) || 0;
decayTime = Number(values[4]) || 0;
startFrequency = Number(values[5]) || 0;
minFrequency = Number(values[6]) || 0;
slide = Number(values[7]) || 0;
deltaSlide = Number(values[8]) || 0;
vibratoDepth = Number(values[9]) || 0;
vibratoSpeed = Number(values[10]) || 0;
changeAmount = Number(values[11]) || 0;
changeSpeed = Number(values[12]) || 0;
squareDuty = Number(values[13]) || 0;
dutySweep = Number(values[14]) || 0;
repeatSpeed = Number(values[15]) || 0;
phaserOffset = Number(values[16]) || 0;
phaserSweep = Number(values[17]) || 0;
lpFilterCutoff = Number(values[18]) || 0;
lpFilterCutoffSweep = Number(values[19]) || 0;
lpFilterResonance = Number(values[20]) || 0;
hpFilterCutoff = Number(values[21]) || 0;
hpFilterCutoffSweep = Number(values[22]) || 0;
masterVolume = Number(values[23]) || 0;
return true;
}
//--------------------------------------------------------------------------
//
// Copying Methods
//
//--------------------------------------------------------------------------
/**
* Returns a copy of this SfxrParams with all settings duplicated
* @return A copy of this SfxrParams
*/
public function clone():SfxrParams
{
var out:SfxrParams = new SfxrParams();
out.copyFrom(this);
return out;
}
/**
* Copies parameters from another instance
* @param params Instance to copy parameters from
*/
public function copyFrom(params:SfxrParams, makeDirty:Boolean = false):void
{
_waveType = params.waveType;
_attackTime = params.attackTime;
_sustainTime = params.sustainTime;
_sustainPunch = params.sustainPunch;
_decayTime = params.decayTime;
_startFrequency = params.startFrequency;
_minFrequency = params.minFrequency;
_slide = params.slide;
_deltaSlide = params.deltaSlide;
_vibratoDepth = params.vibratoDepth;
_vibratoSpeed = params.vibratoSpeed;
_changeAmount = params.changeAmount;
_changeSpeed = params.changeSpeed;
_squareDuty = params.squareDuty;
_dutySweep = params.dutySweep;
_repeatSpeed = params.repeatSpeed;
_phaserOffset = params.phaserOffset;
_phaserSweep = params.phaserSweep;
_lpFilterCutoff = params.lpFilterCutoff;
_lpFilterCutoffSweep = params.lpFilterCutoffSweep;
_lpFilterResonance = params.lpFilterResonance;
_hpFilterCutoff = params.hpFilterCutoff;
_hpFilterCutoffSweep = params.hpFilterCutoffSweep;
_masterVolume = params.masterVolume;
if (makeDirty) paramsDirty = true;
}
//--------------------------------------------------------------------------
//
// Util Methods
//
//--------------------------------------------------------------------------
/**
* Clams a value to betwen 0 and 1
* @param value Input value
* @return The value clamped between 0 and 1
*/
private function clamp1(value:Number):Number { return (value > 1.0) ? 1.0 : ((value < 0.0) ? 0.0 : value); }
/**
* Clams a value to betwen -1 and 1
* @param value Input value
* @return The value clamped between -1 and 1
*/
private function clamp2(value:Number):Number { return (value > 1.0) ? 1.0 : ((value < -1.0) ? -1.0 : value); }
/**
* Quick power function
* @param base Base to raise to power
* @param power Power to raise base by
* @return The calculated power
*/
private function pow(base:Number, power:int):Number
{
switch(power)
{
case 2: return base*base;
case 3: return base*base*base;
case 4: return base*base*base*base;
case 5: return base*base*base*base*base;
}
return 1.0;
}
/**
* Returns the number as a string to 4 decimal places
* @param value Number to convert
* @return Number to 4dp as a string
*/
private function to4DP(value:Number):String
{
if (value < 0.0001 && value > -0.0001) return "";
var string:String = String(value);
var split:Array = string.split(".");
if (split.length == 1)
{
return string;
}
else
{
var out:String = split[0] + "." + split[1].substr(0, 4);
while (out.substr(out.length - 1, 1) == "0") out = out.substr(0, out.length - 1);
return out;
}
}
}