Box2D Sound Spectrum

This week is really challenging : my second year school project has started this week. It’s a team project with a graphic designer, a designer, a project manager and me as a developer. Our concept sounds very promising!

No matter, this short introduction explained that I’m really busy, so this experimentation is not perfect due to a lack of time…
Anyway, click here to see a sound spectrum made with Box2D and the Citrus Engine using BitmapData.

To create this effect, I used Box2D ApplyImpulse method on my Bars. I also used one Rope Joint by bar to link them all to an horizontal platform and try to not make them bounces everywhere. Uncomment the line box2D.visible = true; to see box2d objects with joints (don’t forget to comment stage bitmap addChilded).
There is a bug, sometimes objects pass between two bars and move them. I tried to change some object params like density, friction, restitution… but can’t find a good result.

Here is a part of the code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package {
 
	import Box2DAS.Common.V2;
 
	import graphics.BarEqualizerArt;
	import graphics.ElementCircleArt;
	import graphics.ElementSquareArt;
 
	import objects.BarEqualizer;
	import objects.Element;
 
	import utils.Const;
 
	import com.citrusengine.core.CitrusEngine;
	import com.citrusengine.core.State;
	import com.citrusengine.objects.platformer.Platform;
	import com.citrusengine.physics.Box2D;
 
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.media.SoundMixer;
	import flash.utils.ByteArray;
	import flash.utils.setTimeout;
 
	/**
	 * @author Aymeric
	 */
	public class SoundSpectrumState extends State {
 
		private var _ce:CitrusEngine;
 
		private var _vectBars:Vector.<BarEqualizer>;
 
		private var _ba:ByteArray;
 
		public function SoundSpectrumState() {
 
			super();
 
			_ce = CitrusEngine.getInstance();
 
			_vectBars = new Vector.<BarEqualizer>();
			_ba = new ByteArray();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			Const.bmpDStage = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0);
			Const.bmpDStage.draw(stage);
 
			var bmpStage:Bitmap = new Bitmap(Const.bmpDStage);
			//addChild(bmpStage);
 
			var box2D:Box2D = new Box2D("Box2D");
			box2D.visible = true;
			add(box2D);
 
			var borderLeft:Platform = new Platform("borderLeft", {y:stage.stageHeight >> 1, height:stage.stageHeight});
			add(borderLeft);
 
			var borderRight:Platform = new Platform("borderRight", {x:stage.stageWidth, y:stage.stageHeight >> 1, height:stage.stageHeight});
			add(borderRight);
 
			var borderTop:Platform = new Platform("borderTop", {x:stage.stageWidth >> 1, width:stage.stageWidth});
			add(borderTop);
 
			var borderBottom:Platform = new Platform("borderBottom", {x:stage.stageWidth >> 1, y:350, width:stage.stageWidth});
			add(borderBottom);
 
			var barEqualizer:BarEqualizer;
			for (var i:uint = 0; i < Const.NBR_BAR; ++i) {
				barEqualizer = new BarEqualizer("barEqualizer" + i, {x:22.5 + Const.BarEqualizerWidth * i, y:stage.stageHeight, width:Const.BarEqualizerWidth, height:Const.BarEqualizerHeight, view:BarEqualizerArt});
				add(barEqualizer);
				_vectBars.push(barEqualizer);
				barEqualizer.initJoint(borderBottom, 22.5 + Const.BarEqualizerWidth * i);
			}
 
			_ce.sound.playSound("music");
 
			setTimeout(_addElements, 2500);
		}
 
		private function _addElements():void {
 
			var element:Element;
			for (var j:uint = 0; j < 12; ++j) {
				element = new Element("Element" + j, {x: 40 + 40 * j, y:50, radius:Math.random() > 0.5 ? Const.ELEMENT_RADIUS : 0});
				element.view = element.radius == Const.ELEMENT_RADIUS ? ElementCircleArt : ElementSquareArt;
				add(element);
			}
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			SoundMixer.computeSpectrum(_ba, true);
 
			for (var i:uint = 0; i < Const.NBR_BAR; ++i) {
 
				_vectBars[i].body.ApplyImpulse(new V2(0, -_ba.readFloat() * Const.STRENGTH), _vectBars[i].body.GetWorldCenter());
			}
		}
 
		override public function destroy():void {
 
			super.destroy();
		}
	}
}

The Box2D Bar class :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package objects{
 
	import Box2DAS.Common.V2;
	import Box2DAS.Dynamics.Joints.b2RopeJoint;
	import Box2DAS.Dynamics.Joints.b2RopeJointDef;
 
	import com.citrusengine.objects.PhysicsObject;
	import com.citrusengine.objects.platformer.Platform;
 
	/**
	 * @author Aymeric
	 */
	public class BarEqualizer extends PhysicsObject {
 
		public function BarEqualizer(name:String, params:Object = null) {
 
			super(name, params);
		}
 
		override public function destroy():void {
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
		}
 
		override protected function createBody():void {
 
			super.createBody();
 
			_body.SetFixedRotation(true);
		}
 
		public function initJoint(platform:Platform, posX:uint):void {
 
			var jointDefPlatform:b2RopeJointDef = new b2RopeJointDef();
			jointDefPlatform.Initialize(body, platform.body, body.GetPosition(), new V2(posX / _box2D.scale, platform.body.GetPosition().y));
 
			var joint:b2RopeJoint = b2RopeJoint(_box2D.world.CreateJoint(jointDefPlatform));
			joint.m_maxLength = 3;
		}
 
	}
}

And Bar graphics art :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package graphics {
 
	import utils.Const;
 
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Rectangle;
 
	/**
	 * @author Aymeric
	 */
	public class BarEqualizerArt extends AArt {
 
		public function BarEqualizerArt() {
 
			super();
 
			_art.graphics.clear();
			_art.graphics.beginFill(Math.random() * 0xFFFFFF);
			_art.graphics.drawRect(0, 0, Const.BarEqualizerWidth, Const.BarEqualizerHeight);
			_art.graphics.endFill();
 
			_bmpD = new BitmapData(Const.BarEqualizerWidth, Const.BarEqualizerHeight);
			_bmpD.draw(_art);
		}
 
		override protected function _ef(evt:Event):void {
 
			var point:Point = new Point((this as DisplayObject).localToGlobal(new Point()).x - Const.BarEqualizerWidth * 0.5, (this as DisplayObject).localToGlobal(new Point()).y - Const.BarEqualizerHeight * 0.5);
			Const.bmpDStage.applyFilter(_bmpD, _bmpD.rect, point, _setBitmapFilter());
			Const.bmpDStage.fillRect(new Rectangle(point.x, 0, Const.bmpDStage.width, Const.bmpDStage.height), 0);
			Const.bmpDStage.copyPixels(_bmpD, _bmpD.rect, point, null, null, true);
		}
	}
}

Objects drop (after music start) don’t use the bitmap data to render graphics but a simple Sprite. This is not optimized!
To make it properly use only one bitmap data (no sprite) and one enter frame in the stat class. Then call a render method on each object (bars and other objects). Finally think to use : bitmapData.lock() and bitmapData.unlock() (and make your bitmap data update between them) for a better control.

The zip file.

If I have some time to focus on it, I will make some improvements… but it’s not my priority. You should have enough information to make a cool Sound Spectrum with box2d 😉

Oh and the music is an Abba cover song, made by Therion!

Leave a Reply

Your email address will not be published. Required fields are marked *