Some time ago I’ve made this Equalizer (sorry, you need to close all other swfs like youtube player etc). I’ve been thinking of describing it here for quite a long time.. No excuse, I ve been lazy
Ok, let’s cut to the chase.
First we need to grab music data. Code below gets global spectrum data, converts sound from stereo to mono, simplifies wave to given amount of values and counts wave average value (some kind of wave normal).
package pl.szataniol.equalizer.music { import flash.events.EventDispatcher; import flash.media.Sound; import flash.media.SoundChannel; import flash.media.SoundMixer; import flash.net.URLRequest; import flash.utils.ByteArray; /** * @author Andrzej Korolczuk (http://blog.szataniol.pl) */ public class Equalizer extends EventDispatcher { private static const CHANNEL_LENGTH : uint = 512; private var music : Sound = new Sound(); private var channel : SoundChannel; private var output : ByteArray = new ByteArray(); public function Equalizer() { super(); initEqualizer(); } private function initEqualizer() : void { music.load(new URLRequest("jam.mp3")); channel = music.play(); } public function step() : void { SoundMixer.computeSpectrum(output); } public function getData(numValues : uint = 16) : EqualizerData { var simplifiedWave : Vector.<Number> = getSimplifiedWave(numValues); var average : Number = 0; for (var i : int = 0;i < simplifiedWave.length;i++) { average += simplifiedWave[i]; } average /= numValues; return new EqualizerData(simplifiedWave, average); } public function getSimplifiedWave(numValues : uint = 16) : Vector.<Number> { var waveValues : Vector.<Number> = new Vector.<Number>(numValues, true); var valueIndex : uint = 0; while(valueIndex < CHANNEL_LENGTH / 2) { waveValues[Math.floor(valueIndex / CHANNEL_LENGTH * numValues)] = Math.abs(output.readFloat()); valueIndex++; } while(valueIndex < CHANNEL_LENGTH) { waveValues[Math.floor((valueIndex - CHANNEL_LENGTH / 2) / CHANNEL_LENGTH * numValues)] += Math.abs(output.readFloat()); valueIndex++; } for (var i : int = 0;i < waveValues.length;i++) { waveValues[i] /= 6; } return waveValues; } public function getOutput() : ByteArray { return output; } } }
Now the part that might not be so clear. Equalizer is created by drawing a movieclip into a bitmap and aplying some effects. Movieclip (i called it Ghost) is consisted of 16 particles (GhostParticles) with blend mode set to “add”. Every GhostParticle contains a blurred white circle:

Here’s how Ghost looks on stage:

Final effect applied on Ghost is glow with knockout:

GhostParticle class looks like this:
package pl.szataniol.equalizer.display.ghost { import flash.display.Sprite; /** * @author Andrzej */ public class GhostParticle extends Sprite { private const MIN_ANGULAR_SPEED : uint = 2; private const MAX_ANGULAR_SPEED : uint = 8; private const MIN_SCALE : Number = .6; private const MAX_SCALE : Number = 1; private const MIN_OFFSET : int = 0; private const MAX_OFFSET : uint = 50; private var gamma : Number; private var angularSpeed : Number; private var offset : Number; private var scale : Number; public var mc : Sprite; private var _boost : Number = 0; public function GhostParticle() { initGhostParticle(); } private function initGhostParticle() : void { initParams(); mc.y = offset; rotation = gamma; scaleX = scaleY = scale; alpha = Math.random() * 5 + .5; } private function initParams() : void { scale = MIN_SCALE + Math.random() * (MAX_SCALE - MIN_SCALE); offset = MIN_OFFSET + Math.random() * (MAX_OFFSET - MIN_OFFSET); gamma = Math.random() * 360; angularSpeed = (Math.random() < .5 ? 1 : -1) * (MIN_ANGULAR_SPEED + Math.random() * (MAX_ANGULAR_SPEED - MIN_ANGULAR_SPEED)); } public function step() : void { gamma += angularSpeed; rotation = gamma; mc.y += ((offset - _boost * 40) - mc.y) / 4; mc.y += (offset - mc.y) / 8; _boost *= .9; scaleX = scale + _boost * 2; } public function set boost(boost : Number) : void { _boost = boost; } } }
Particles have several randomized variables: alpha, angularSpeed, offset and scale. Offest is circle sprite “y” value. Each particle is moving in a circular motion with radius based on offset and “boost”, which represents current simplified sound wave value.
Ghost class calls step() function and delegates boost value to each particle.
Ok now that we have our base MovieClip ready we can start some fun with BitmapData. The cycle looks like this:
- calculate color for ColorTransform (each channel value is based on sine function)
- if wave normal is greater then some value and last glow wasn’t applied in 6 frames apply it.
- recalculate perlin noise used for DisplacementMapFilter
- apply DisplamcentMapFilter
- draw ghost MovieClip with alpha based on wave normal
- scroll BitmapData by value based on wave normal
- apply ColorTransform
- apply BlurFilter
Here’s the code:
package pl.szataniol.equalizer.display { import flash.filters.DisplacementMapFilterMode; import flash.filters.DisplacementMapFilter; import flash.display.BitmapDataChannel; import flash.geom.Rectangle; import flash.filters.GlowFilter; import pl.szataniol.equalizer.display.ghost.Ghost; import pl.szataniol.equalizer.music.Equalizer; import pl.szataniol.equalizer.music.EqualizerData; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.filters.BlurFilter; import flash.geom.ColorTransform; import flash.geom.Matrix; import flash.geom.Point; /** * @author Andrzej */ public class EqualizerMain extends Sprite { private var equalizer : Equalizer = new Equalizer(); public var ghost : Ghost; private var perlinBitmapData : BitmapData = new BitmapData(160, 120, true, 0x0); private var bitmapData : BitmapData = new BitmapData(640, 480, true, 0x0); private var bitmap : Bitmap = new Bitmap(bitmapData); private var zero : Point = new Point(0, 0); private var matrix : Matrix ; private var colorTransform : ColorTransform = new ColorTransform(0.98, .98, 1, .92); private var redMultiplier : Number; private var redGamma : Number = Math.random() * Math.PI * 2; private var blueMultiplier : Number; private var blueGamma : Number = Math.random() * Math.PI * 2; private var greenMultiplier : Number; private var greenGamma : Number = Math.random() * Math.PI * 2; private var redSpeed : Number = 0.01 + Math.random() * 0.02; private var blueSpeed : Number = 0.01 + Math.random() * 0.02; private var greenSpeed : Number = 0.01 + Math.random() * 0.02; private var bitmapDataRect : Rectangle; private var lastBoost : int = 0; private var blurFilter : BlurFilter = new BlurFilter(2, 8, 3); public function EqualizerMain() { initEqualizerMain(); } private function initEqualizerMain() : void { addChild(bitmap); removeChild(ghost); addEventListener(Event.ENTER_FRAME, enterFrameHandler); matrix = new Matrix(ghost.scaleX, 0, 0, ghost.scaleY, ghost.x, ghost.y); bitmapDataRect = bitmap.bitmapData.rect; perlinBitmapData.perlinNoise(120, 120, 1, Math.random(), false, true, BitmapDataChannel.RED | BitmapDataChannel.GREEN); } private function enterFrameHandler(event : Event) : void { step(); } private function step() : void { equalizer.step(); ghost.step(); handleColors(); var eqData : EqualizerData = equalizer.getData(16); ghost.boost = eqData.wavesData; matrix.a = matrix.d = Math.max(.75, Math.min(eqData.average, 1.2)); lastBoost++; if(eqData.average > .7 && lastBoost > 6) { lastBoost = 0; bitmap.bitmapData.applyFilter(bitmap.bitmapData, bitmap.bitmapData.rect, zero, new GlowFilter(0xffffff, .8, 32, 32, .25 + eqData.average * 1.8, 3)); } perlinBitmapData.perlinNoise(120, 120, 1, Math.random(), false, true, BitmapDataChannel.RED | BitmapDataChannel.GREEN); bitmap.bitmapData.applyFilter(bitmap.bitmapData, bitmapDataRect, zero, new DisplacementMapFilter(bitmap.bitmapData, zero, BitmapDataChannel.RED, BitmapDataChannel.GREEN, 12, 12, DisplacementMapFilterMode.IGNORE, 0)); bitmap.bitmapData.draw(ghost, matrix, new ColorTransform(1, 1, 1, .1 + Math.max(.9, eqData.average))); bitmap.bitmapData.scroll(0, -2 - eqData.average * 10); bitmap.bitmapData.colorTransform(bitmap.bitmapData.rect, colorTransform); bitmap.bitmapData.applyFilter(bitmap.bitmapData, bitmap.bitmapData.rect, zero, blurFilter); } private function handleColors() : void { redGamma += redSpeed; greenGamma += greenSpeed; blueGamma += blueSpeed; redMultiplier = .95 + Math.sin(redGamma) * .05; blueMultiplier = .95 + Math.sin(blueGamma) * .05; greenMultiplier = .95 + Math.sin(greenGamma) * .05; colorTransform.redMultiplier = redMultiplier; colorTransform.blueMultiplier = blueMultiplier; colorTransform.greenMultiplier = greenMultiplier; } } }
That’s about it. You can download sources here. Song I used is Eastern Jam by Chase and Status.


