Over the time I created an easy way to get projects up and running without losing too much time in the logic.
There are a lot of frameworks that do this, and more, but I find that mine is very easy to work with, and I use it in all my projects. love™ isn´t exactly the best name, but its better the foo… right?!
For this post I decided to release a lighter version of the framework, not because I want to keep it a secret, but because there´s no need to complicate what I want to illustrate. Complete version of the framework will be released soon.
The Framework is divided in 3 parts.
-CORE (with events, settings, and logic)
-MEDIA (with custom buttons, animation engines, video players etc…)
-UTILS (with String validatores, math utilities, bitmap utilities, garbage collection, etc)
for this version i significant reduced the MEDIA folder, and the UTILS folder, leaving just the basic to understand and get you running on my framework.
Here is a quick example:
You can download the source here.
Open your favorite actionscript editor and import the framework and the TweenMax engine used in this demo.
In your document class import StageDefinition.as and create a class (in this case called App.as that will extend Application.as
package { import flash.display.Sprite; import love.core.setup.StageDefinition; import pt.example.view.App; public class Main extends Sprite { public function Main() { var root:StageDefinition = StageDefinition.getInstance(); root.start(this); addChild(new App()); } } }
So far, we have set the stage definitions, the custom right click menu (this can be override), and we added the App.as class to the stage.
The App.as is the visuals of your project.
package pt.example.view { import flash.events.Event; import love.core.Application; import love.core.events.AppEvent; import love.core.setup.Structure; import love.media.button.Semi3D; import pt.example.view.page.Simple; import pt.example.view.sobre.Sobre; public class App extends Application { private var botao1:Semi3D; private var botao2:Semi3D; public function App() { var areas:Structure = Structure.getInstance(); areas.addArea("about", pt.example.view.sobre.Sobre); areas.addArea("simple_page", pt.example.view.page.Simple); this.addEventListener(Event.ADDED_TO_STAGE, onAdded, false, 0, true); } public override function Navigation(id:String):void { switch(id) { case "about": botao1.enable(false); botao2.enable(true); break; default: botao1.enable(true); botao2.enable(false); break; } } private function onAdded(e:Event):void { this.removeEventListener(Event.ADDED_TO_STAGE, onAdded); botao1 = new Semi3D(click1); botao1.config("About the framework", null, 0xFFFFFF, 0xCCCCCC, 4, true); botao1.enable(true); botao1.x = 20; botao1.y = 20; addChild(botao1); botao2 = new Semi3D(click2); botao2.config("Simple Page", null, 0xFFFFFF, 0xCCCCCC, 4, true); botao2.enable(true); botao2.x = botao1.x + botao1.width + 10; botao2.y = botao1.y; addChild(botao2); dispatchEvent(new AppEvent(AppEvent.NAVIGATE, {area:"about"})); } private function click1():void { dispatchEvent(new AppEvent(AppEvent.NAVIGATE, {area:"about"})); } private function click2():void { dispatchEvent(new AppEvent(AppEvent.NAVIGATE, {area:"simple_page"})); } } }
As you have noticed the App.as extends Application.as that handles the views and dispatches events throw the StageDefinition.STAGE (your stage) informing on AppEvent.Navigation type events.
In this example I´ve added 2 pages and 2 buttons.
Every page in a view, and it extends a Base.as class.
Base.as class handles the open and close methods, resize and remove methods, your can override all of this methods, to create custom open and close animations from page to page. In this example I use the default from Right to Left animation (using TweenMax).
The complete version of the framework has Papervision3D and SWFAddress support, custom debug tools, custom buttons, animation engine, and some great utilities to rapidly starting creating your website.
Cheers
André
I almost like easter eggs more than moustaches
January 26th, 2011
Post in Actionscript 3.0, class, tutorialsSometimes I like to pretend that in every website I do, I have and easter egg.
Sometimes I share it with friends and sometimes I dont. Its a kind of therapy that helps me keep excited about a project. Weird I know.
So, this is how I usually do it.
Somewhere in your Application, you should have this code
package { import flash.display.Sprite; import love.utils.Cheats; public class Main extends Sprite { public function Main() { var cheats:Cheats = Cheats.getInstance(); cheats.addWord("lorem ipsum", cheat1); cheats.addWord("user has no moustache", cheat2); cheats.enable(true); } public function cheat1():void { trace("do some shit"); } public function cheat2():void { trace("close website, user is lamme!"); } } }
So, if the user for some reason type in the sentence “LOREM IPSUM” it automatically call the cheat1 function. This adds a lot of creative possibilities.
package love.utils { import flash.events.KeyboardEvent; import love.core.debug.TheDebugger;//this class Is just for debugging, you can change the TheDebugger.lot() to trace(). import love.core.setup.StageDefinition;//This class stores the stage reference, you can change the StageDefinition.STAGE to Stage. public class Cheats { protected static var instance:Cheats; private var words:Array = []; private var possible:Array = []; private var currentChar:uint = 0; public function Cheats() { if(instance != null)TheDebugger.error("Cheats Singleton already constructed!"); instance = this; } public static function getInstance():Cheats { if(instance == null)instance = new Cheats(); return instance; } public function addWord(word:String, func:Function):void { var obj:Object = new Object(); obj.word = word; obj.func = func; words.push(obj); } public function enable(value:Boolean):void { if(value) { StageDefinition.STAGE.addEventListener(KeyboardEvent.KEY_DOWN, checkForSequence); } } private function checkForSequence(e:KeyboardEvent):void { var valid:Boolean = false; for(var i:uint = 0; i<words.length;++i) { if(checkLetterInWord(e.charCode, words[i].word)) { possible.push(words[i]); valid = true; } } //var char:String = String.fromCharCode(e.charCode); checkValidWord(valid); possible =[]; } private function checkValidWord(valid:Boolean):void { if(valid) { currentChar++; if(currentChar == possible[0].word.length) { possible[0].func(); resetCharCount(); } } else { resetCharCount() } } private function resetCharCount():void { currentChar = 0; } private function checkLetterInWord(code:Number, word:String):Boolean { return code == word.charCodeAt(currentChar) ? true : false; } } }
Now, lets take this baby for a ride
You can download the source here.
I thought I could share a piece of code.
This is a work in progress, but I thought this could come in handy to someone out there.
import av.events.SiteEvent; import av.utils.LoadFont; import av.effects.BackgroundText; var font1:LoadFont = new LoadFont("fonts/AauxProBold.swf"); font1.addEventListener(SiteEvent.LOAD_FONT_COMPLETE, loadComplete); var style:TextFormat; var texts:Array = [ "Hello! I created this Class as an Example for Creating multiple lines using only one String.", "I added a background because as you know, the 'backgroundColor' property of a TextField() sucks!!!", "This was made for a very specific purpose, but I thought maybe this could be helpful to someone.", "This is just and experiment. It´s not ready for a public release yet, so feel free to download the source and use it as you like." ]; var actual:Number; function loadComplete(e:SiteEvent):void { style= new TextFormat("AauxProBold", 16, 0xFFFFFF); style.letterSpacing = 0; style.kerning = true; style.align = "left"; DemoSTART(); } function DemoSTART():void { actual = 0; var _title:BackgroundText = new BackgroundText(); _title.y = 50; _title.addEventListener("ALL_DONE", DemoEND); addChild(_title) for(var i:uint=0;i<texts.length;++i) { _title.Config(texts[i], style, 150); } _title.animIN(); } function DemoEND(e:Event):void { trace("Acabou"); var _over:Sprite = new Sprite(); _over.addEventListener(MouseEvent.CLICK, doRestart); _over.buttonMode = true; addChild(_over); _over.graphics.clear(); _over.graphics.beginFill(0,0) _over.graphics.drawRect(0,0,400,300) _over.graphics.endFill(); var tf:TextField = new TextField(); tf.text = " CLICK ANYWHERE TO RESTART"; tf.autoSize = "left"; tf.border = true; tf.borderColor = 0xFF0000; tf.selectable = false; tf.mouseEnabled = false; tf.embedFonts = true; tf.defaultTextFormat = style; tf.setTextFormat(style); _over.addChild(tf); tf.x = (400/2) - (tf.width/2); tf.y = (300/2) - (tf.height/2); } function doRestart(e:MouseEvent):void { var total:uint =numChildren-1 for(var i:uint = 0;i <total;++i) { removeChildAt(1); } DemoSTART(); }
package av.effects { import flash.display.Sprite; import flash.events.Event; import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextLineMetrics; import flash.utils.setTimeout; import gs.TweenLite; import gs.easing.*; public class BackgroundText extends Sprite { private var dados:Array = []; private var currentTween:uint = 0; private var assets:Array = []; private var txt:TextField; private var tf:TextFormat; public function BackgroundText() { addEventListener("ANIM_IN_COMPLETE", checkForNewMessages); addEventListener("ANIM_OUT_COMPLETE", loadNextIfAny); } public function Config(_texto:String, _textFormat:TextFormat, _width:uint):void { tf = _textFormat; txt = new TextField(); txt.width = _width; txt.wordWrap = true; txt.autoSize = "left"; txt.text = _texto; var frases:Array = []; for(var i:uint = 0;i<txt.numLines;i++) { var OBJ:Object = new Object(); OBJ.__text = txt.getLineText(i); OBJ.__x = 0; OBJ.__y = 22 * i; OBJ.__delay = .3 * i; frases.push(OBJ); } dados.push(frases); } private function checkForNewMessages(e:Event):void { //trace("IN complete"); currentTween++ if(currentTween<dados.length) { setTimeout(animOUT, 4000); } else { dispatchEvent(new Event("ALL_DONE")); } } private function loadNextIfAny(e:Event):void { //trace("OUT complete"); if(currentTween<dados.length) { animIN() } } public function animIN():void { for(var i:uint = 0;i<dados[currentTween].length;i++) { setBackground(dados[currentTween][i].__text, dados[currentTween][i].__x, dados[currentTween][i].__y, dados[currentTween][i].__delay, i<dados[currentTween].length-1 ? false : true); } } private function setBackground(__texto:String, __x:int, __y:int, __delay:Number, _isLast:Boolean = false):void { var temp:Sprite = new Sprite(); var tempBG:Sprite = new Sprite() var tempTXT:Sprite = new Sprite() var mascara:Sprite = new Sprite(); addChild(temp); addChild(mascara); temp.addChild(tempBG); temp.addChild(tempTXT); temp.x = __x; temp.y = __y; mascara.x = __x; mascara.y = __y; tempBG.graphics.beginFill(0x6699cc); tempBG.graphics.drawRect(0, 0, 10, 10); tempBG.graphics.endFill() mascara.graphics.beginFill(0x000000,0); mascara.graphics.drawRect(0, 0, 10, 10); mascara.graphics.endFill() var txt:TextField = new TextField(); txt.text = __texto; txt.textColor = 0xFFFFFF; txt.autoSize = "left"; txt.selectable = false; txt.embedFonts = true; txt.defaultTextFormat = tf; txt.setTextFormat(tf); temp.addChild(txt); var tlm:TextLineMetrics = txt.getLineMetrics(0); tempBG.width = txt.width; tempBG.y = tlm.descent; tempBG.height = tlm.ascent + tlm.descent; temp.mask = mascara; mascara.width = 0; mascara.height = txt.height; assets.push(mascara); TweenLite.to(mascara, .5, {width: txt.width, delay:__delay,ease:Quad.easeInOut, onComplete:function():void { if(_isLast)dispatchEvent(new Event("ANIM_IN_COMPLETE")); }}); } public function animOUT():void { assets.reverse(); for(var i:int = 0;i<assets.length;++i) { if(i<assets.length-1) { TweenLite.to(assets[i], .5, {width: 0, delay:.3 * i, ease:Quad.easeInOut}); } else { TweenLite.to(assets[i], .5, {width: 0, delay:.3 * i, ease:Quad.easeInOut, onComplete:function():void { var total:uint = numChildren; for(var i:uint = 0; i<numChildren;++i) { removeChildAt(0); } dispatchEvent(new Event("ANIM_OUT_COMPLETE")); }}) } } assets = []; } } }
I wanted to create an effect for image transitions that would be similar to a glass breaking. And during my experiments I discovered some things I want to share.
Imagine your image is 400x300px and you want to divide it in 6 different parts.
You want the 1st part to be located at the 0x position, the 2nd at the 70px position and so on…
(0, 70, 100, 220, 330, 350)
So basically, your divisions over the image will be 70px for the 1st part, 30px for the 2nd and so on.
(70, 30, 120, 110, 20, 50)
And that´s it, your now able to divide a 400x300px image into 6 boring rectangles.
But, broken glass is more similar to triangles, not rectangles, so we still must divide every rectangle into 2 triangles, and this is where the fun starts.
There are no graphics.drawTriangle() methods in the graphics Class, so how can we draw triangles?
Using the graphics.moveTo() and the graphics.LineTo() methods.
The graphics.LineTo() draws a line from a given x and y coordinates to a registration point (defaults x0 y0), so, every time you want to change that registration point you must use the graphics.moveTo() method.
That said, let´s draw some triangles.
Heres how it´s done:
var W:Number = 400; var H:Number = 300; var divisions:Array = [0,70,100,220,330,350]; var sizes:Array = []; for (var j:uint =1; j<=divisions.length; ++j) { var value:Number; if (j < divisions.length) { value = divisions[j] - divisions[j - 1]; } else { value = W - divisions[j - 1]; } sizes.push(value); } for (var i:uint = 0; i<divisions.length; ++i) { drawTriangles(divisions[i], sizes[i], H); } function drawTriangles(mX:Number, sizeW:Number, H:Number):void { graphics.beginFill(Math.random()*0xFF0000); graphics.moveTo(mX,0); graphics.lineTo(mX + sizeW,0); graphics.lineTo(mX + sizeW,H); graphics.beginFill(Math.random()*0x00FF00); graphics.moveTo(mX,0); graphics.lineTo(mX + 0,H); graphics.lineTo(mX + sizeW,H); }
With a bit of imagination, you can see glass somewhere… Hopefully!
You can download the source here.
So I have to do a calendar and implement it on an AS3 website? No problem! (I thought!…) Where the hell is the calendar component that we had in AS2? And why couldn´t I find a way to use the one in Flex?
Enough with the questions, here are some answers.
Document Class
package { import com.andrevenancio.component.Calendar; import flash.display.Sprite; public class Main extends Sprite { private var calendar:Calendar; public function Main() { calendar = new Calendar(); calendar.x = 50; calendar.y = 50; addChild(calendar); var _data:Date = new Date(); calendar.month = _data.getMonth(); calendar.year = _data.getFullYear(); calendar.Render(); } } }
And here is the Calendar Class
package com.andrevenancio.component { import flash.display.Sprite; import flash.events.MouseEvent; import flash.text.TextField; import flash.text.TextFormat; public class Calendar extends Sprite { /** TEXT FORMAT */ private var month_tf:TextFormat; private var week_tf:TextFormat; private var days_tf:TextFormat; private var button_tf:TextFormat; /** LANGUAGE CONFIG */ private var _months:Array = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] private var _days:Array = ["S", "M", "T", "W", "T", "F", "S"] /** DAYS PER MONTH */ private var _monthTotal:Array = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); /** CALENDAR */ private var _today:Date = new Date(); private var _targetMonth:uint = _today.getMonth(); private var _targetYear:uint = _today.getFullYear(); /** VISUAL ASSETS */ private var _selectedMonth:TextField; private var _daysWeek:Sprite = new Sprite(); private var _nextBT:Sprite = new Sprite(); private var _prevBT:Sprite = new Sprite(); /** HOLDER FOR DAYS OF THE MONTH */ private var _holder:Sprite = new Sprite(); public function Calendar() { configStyle(); build(); } /** * Style Configurations * */ private function configStyle():void { month_tf = new TextFormat("Arial", 14, 0x333333, true); month_tf.align = "center"; week_tf = new TextFormat("Arial", 10, 0x666666, true); week_tf.align = "center"; days_tf = new TextFormat("Arial", 10, 0x666666); days_tf.align = "center"; button_tf = new TextFormat("Arial", 10, 0xFFFFFF); button_tf.align = "center"; } /** * Builds interface * */ private function build():void { /** Month and Year TextField */ _selectedMonth = new TextField(); _selectedMonth.text = ""; _selectedMonth.border = _selectedMonth.selectable = false; _selectedMonth.width = 140; _selectedMonth.height = 20; _selectedMonth.defaultTextFormat = TextFormat(month_tf); addChild(_selectedMonth); /** Days of the week */ addChild(_daysWeek); for(var i:uint = 0; i<7;++i) { var temp:Sprite = new Sprite(); temp.graphics.beginFill(0,0); temp.graphics.drawRect(0,0, 20, 20); temp.graphics.endFill(); temp.x = 20 * i; temp.y = _selectedMonth.y + _selectedMonth.height; addChild(temp); writeText(_days[i], week_tf, temp); } /** Buttons */ _prevBT.graphics.beginFill(1); _prevBT.graphics.drawRect(0, 0, 16, 16); _prevBT.graphics.endFill(); _nextBT.graphics.beginFill(0); _nextBT.graphics.drawRect(0, 0, 16, 16); _nextBT.graphics.endFill(); addChild(_prevBT); addChild(_nextBT); writeText("<<", button_tf, _prevBT); writeText(">>", button_tf, _nextBT); _prevBT.x = -_prevBT.width; _prevBT.y = 5; _nextBT.x = _selectedMonth.width; _nextBT.y = 5; _nextBT.buttonMode = _prevBT.buttonMode = true; _prevBT.addEventListener(MouseEvent.CLICK, prevMonth); _nextBT.addEventListener(MouseEvent.CLICK, nextMonth); /** Holder for days of the month */ addChild(_holder); _holder.y = _selectedMonth.y + _selectedMonth.height; } /** * decreases current month and year if necessary * */ private function prevMonth(e:MouseEvent):void { _targetMonth = _targetMonth == 0 ? 12 : _targetMonth % 12; _targetMonth-- _targetMonth % 12 == 11 ? _targetYear-- : null; Render(); } /** * increments current month and year if necessary * */ private function nextMonth(e:MouseEvent):void { _targetMonth++ _targetMonth = _targetMonth == 12 ? 0 : _targetMonth % 12; _targetMonth % 12 == 0 ? _targetYear++ : null; Render(); } /** * Write text in some sprite * */ private function writeText(_text:String, _textFormat:TextFormat, _target:Sprite):void { var label_txt:TextField = new TextField(); label_txt.text = _text; label_txt.border = label_txt.selectable = label_txt.mouseEnabled = false; label_txt.autoSize = "left"; label_txt.defaultTextFormat = TextFormat(_textFormat); label_txt.setTextFormat(_textFormat); _target.addChild(label_txt); } /** * Remove all days of the month (sprites). * */ private function cleanGraphics():void { var total:uint = _holder.numChildren; for(var i:uint = 0; i < total;++i) { _holder.removeChildAt(0); } } /** * Renders Calendar info * */ public function Render():void { /** removes all unnecessary assets */ cleanGraphics(); /** changes calendar month and year */ _selectedMonth.text = _months[_targetMonth % 12] + " " + _targetYear; /** draws calendar based on year and month */ var daysNo:uint = (_targetYear % 4 == 0 && _targetMonth % 12 == 1 ? 29 : _monthTotal[_targetMonth % 12]); /** gets 1st day of the week in current selected month */ var temp:Date = new Date(_targetYear, _targetMonth); var startDay:uint = temp.getDay(); /** Adds sprite with days */ var row:Number = 0; for (var i:uint = 1; i < daysNo+1; i++) { var sp:Sprite = new Sprite(); sp.x = startDay * 20; sp.y = (row+1) * 20; _holder.addChild(sp); writeText(i.toString(), days_tf, sp); startDay++; /** changes row on saturdays */ if(startDay >= 7) { startDay = 0; row++; } } } /** * SETTERS GETTERS * */ public function set month(_value:uint):void {_targetMonth = _value;} public function set year(_value:uint):void {_targetYear = _value;} public function get month():uint {return _targetMonth;} public function get year():uint {return _targetYear;} } }
I have created some setters and getters, because in my case I wanted to call a Zend AMF Service that returned all the events for the specified month.
You can download the source here.
With Flash 10, we can easily change cursors with the flash.ui.MouseCursor Class.
This is a simple example with all the available cursors. Read more about it here .
I´ve created a Sprite with MouseEvents as otherwise the cursors will change even if your mouse is not over the SWF. I’ve got to be honest, I don´t find this very helpful. I appreciate any comments, because I really don´t see the point of the cursor not changing back to “normal” if your mouse is not over the SWF. Hope this can be fixed soon.
package { import flash.display.Sprite; import flash.events.MouseEvent; import flash.events.TimerEvent; import flash.ui.Mouse; import flash.ui.MouseCursor; import flash.utils.Timer; public class Main extends Sprite { private var _stage:Sprite = new Sprite(); private var _current:uint = 0; private var cursors:Array = [MouseCursor.ARROW, MouseCursor.BUTTON, MouseCursor.HAND, MouseCursor.IBEAM]; private var _timer:Timer; public function Main() { addChild(_stage); _stage.graphics.beginFill(0, 0); _stage.graphics.drawRect(0, 0, 400, 300); _stage.graphics.endFill(); _timer = new Timer(200); _timer.addEventListener(TimerEvent.TIMER, doMouseChange); _stage.addEventListener(MouseEvent.MOUSE_OVER, onOver); _stage.addEventListener(MouseEvent.MOUSE_OUT, onLeave); } private function onOver(e:MouseEvent):void { _timer.start(); } private function onLeave(e:MouseEvent):void { _timer.stop(); Mouse.cursor = cursors[0]; } private function doMouseChange(e:TimerEvent):void { Mouse.cursor = cursors[_current % cursors.length]; ++_current; } } }
You can download the source here.
It´s always good to keep track of performance.
package { import com.andrevenancio.debug.FPS; import flash.display.Sprite; public class Main extends Sprite { public function Main() { addChild(new FPS()); } } }
package com.andrevenancio.debug { import flash.display.Sprite; import flash.events.Event; import flash.text.TextField; import flash.utils.getTimer; public class FPS extends Sprite { private var time:Number; private var frameTime:Number; private var prevFrameTime:Number = getTimer(); private var secondTime:Number; private var prevSecondTime:Number = getTimer(); private var frames:Number = 0; private var fps:String = ""; private var bar:Sprite = new Sprite(); private var tf:TextField = new TextField(); public function FPS() { addEventListener(Event.ADDED_TO_STAGE, onAdded) } private function onAdded(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, onAdded) addChild(bar) bar.graphics.beginFill(0xFF0000); bar.graphics.drawRect(0, 0, 50, 14); bar.graphics.endFill(); tf.autoSize = "left"; tf.selectable = false; tf.textColor = 0xFFFFFF; addChild(tf); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(e:Event):void { time = getTimer(); frameTime = time - prevFrameTime; secondTime = time - prevSecondTime; if (secondTime >= 1000) { fps = frames.toString(); frames = 0; prevSecondTime = time; } else { frames++; } prevFrameTime = time; tf.text = ((fps + " fps / ") + frameTime) + " ms"; bar.scaleX = bar.scaleX - ((bar.scaleX - (frameTime /10)) / 5); } } }
You can download the source here.
This post should be named Energy rather than Beat Detection, but I think this title is more catchy. Beats are usually on the lower part of the Sound Spectrum. Usually a kick in the drumset. There are some algorithm for detecting a beat, and they work just fine in any DJ software or turntables. But if you´re not Andre Michelle you would like to use a simple (pseudo) beat detection using sound energy peaks.
To do this, we must detect sound energy variations by computing the average sound energy of a signal and comparing it to the instant sound energy. We detect a beat only when the energy is superior to a local energy average. You can read more about it here.
package { import com.andrevenancio.audio.BeatDetection; import flash.display.Sprite; public class Main extends Sprite { public function Main() { var _beat:BeatDetection = new BeatDetection("http://blog.andrevenancio.com/mp3/sample.mp3"); addChild(_beat); } } }
package com.andrevenancio.audio { import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.events.TimerEvent; import flash.geom.ColorTransform; import flash.media.Sound; import flash.media.SoundChannel; import flash.media.SoundMixer; import flash.net.URLRequest; import flash.text.TextField; import flash.utils.ByteArray; import flash.utils.Timer; public class BeatDetection extends Sprite { private var _channel:SoundChannel; private var _sound:Sound; private var _timer:Timer; private var _link:String; private var _play:Sprite = new Sprite(); private var spectrumBars_mc:Sprite = new Sprite(); private var beatMinimum_mc:Sprite = new Sprite(); private var averageMagnitude_mc:Sprite = new Sprite(); private var currentMagnitude_mc:Sprite = new Sprite(); private var beat:Sprite = new Sprite(); private var channels:Number = 256; private var ba:ByteArray = new ByteArray(); private var media:Array; private var valor:int; private var maxUpLimit:uint = 200; private var multiplier:uint = 100; private var startRange:uint = 0; private var stopRange:uint = 128; //this value changes from song to song. private var factor:uint = 64; public function BeatDetection(_mp3:String) { _link = _mp3; config(); } private function config():void { var _text:TextField = new TextField() _text.text = "click here to play"; _text.autoSize = "left"; _text.selectable = _text.mouseEnabled = false; _text.textColor = 0xFFFFFF; _play.addChild(_text); addChild(_play); _play.x = 400/2 - _play.width/2; _play.y = 300/2 - _play.height/2; _play.graphics.beginFill(0x336699); _play.graphics.drawRect(0,0, _text.width, _text.height); _play.graphics.endFill() _play.buttonMode = true; _play.addEventListener(MouseEvent.CLICK, doPlay); } private function doPlay(e:MouseEvent):void { _play.removeEventListener(MouseEvent.CLICK, doPlay); removeChild(_play); //create visuals addChild(beat); beat.alpha = 0; beat.graphics.beginFill(0xFF0000); beat.graphics.drawRect(0, 0, 400, 300) beat.graphics.endFill(); addChild(spectrumBars_mc); spectrumBars_mc.x = (400-256) / 2; spectrumBars_mc.y = 200; spectrumBars_mc.addChild(beatMinimum_mc) spectrumBars_mc.addChild(averageMagnitude_mc); spectrumBars_mc.addChild(currentMagnitude_mc); addBar("Treshold", 0xFF0000, beatMinimum_mc); addBar("Average amplitude", 0x00CC00, averageMagnitude_mc); addBar("Current amplitude", 0x000099, currentMagnitude_mc); //loads sound _sound = new Sound(new URLRequest(_link)); _sound.addEventListener(Event.COMPLETE, loadComplete); } private function addBar(_title:String, _color:int, _targetSP:Sprite):void { _targetSP.graphics.beginFill(0x000000); _targetSP.graphics.drawRect(0, 0, channels, 1); _targetSP.graphics.endFill(); var colorTransform:ColorTransform = _targetSP.transform.colorTransform; colorTransform.color = _color; _targetSP.transform.colorTransform = colorTransform; } private function loadComplete(e:Event):void { _sound.removeEventListener(Event.COMPLETE, loadComplete); _sound = e.target as Sound; _channel = _sound.play(); _channel.addEventListener(Event.SOUND_COMPLETE, reStart); _sound.play(); //starts timer for beat detection _timer = new Timer(26); _timer.addEventListener(TimerEvent.TIMER, getCurrentMagnitude); _timer.start(); } public function reStart(e:Event):void { _timer.stop(); _timer = null; _channel.stop(); _sound = null; removeChild(spectrumBars_mc); config(); } private function getCurrentMagnitude(e:TimerEvent):void { SoundMixer.computeSpectrum(ba,true,0); spectrumBars_mc.graphics.clear(); media = new Array(); for (var i:int=0; i < channels; ++i) { valor = ba.readFloat() * multiplier; media.push(valor); spectrumBars_mc.graphics.beginFill(0xFFFFFF); spectrumBars_mc.graphics.drawRect(i, 0, 1, -valor ); } getAverageMagnitude() } /** * BEAT DETECTION STARTS HERE * */ private function getAverageMagnitude():void { if (media[0] != null && media.length >= 1) { var aLength:uint = media.length; var average:uint = 0; var max:uint = 0; var med:uint = 0; for (var i:uint = 0; i <= aLength; ++i) { if (media[i] is Number) { average += media[i]; max=media[i]>max?media[i]:max; } } currentMagnitude_mc.y =- max; med = (max / 2); average = average/aLength; averageMagnitude_mc.y = -med; beatMinimum_mc.y = -(med+factor); if (currentMagnitude_mc.y<beatMinimum_mc.y) { //WE DETECTED A BEAT!! beat.alpha=1; } else { beat.alpha=0; } } } } }
You can download the source here.
I´m a musician and my degree is in Production and Music Technology, so, I was very happy when Adobe released the computeSpectrum functionality.
This is my StereoPlane. StereoPlane is an audio visualization using a free loop. For a cleaner code, I decided to separate all the papervision3D basic stuff into another class.
Here is my class holding all the pv3d settings
package com.andrevenancio.pv3d { import flash.display.Sprite; import flash.events.Event; import org.papervision3d.cameras.Camera3D; import org.papervision3d.render.BasicRenderEngine; import org.papervision3d.scenes.Scene3D; import org.papervision3d.view.Viewport3D; public class Basic3D extends Sprite { //basic papervision vars public var viewport:Viewport3D; public var renderer:BasicRenderEngine; public var scene:Scene3D; public var camera:Camera3D; public function Basic3D() { addEventListener(Event.ADDED_TO_STAGE, startPV3D, false, 0, true); } private function startPV3D(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, startPV3D); viewport = new Viewport3D(400, 300, true, true); renderer = new BasicRenderEngine(); scene = new Scene3D(); camera = new Camera3D(); addChild(viewport); addEventListener(Event.ENTER_FRAME, render); } public function render(e:Event):void { renderer.renderScene(scene, camera, viewport); } } }
And this is my DocumentClass
package { import com.andrevenancio.pv3d.Basic3D; import flash.display.MovieClip; import flash.events.Event; import flash.events.MouseEvent; import flash.media.Sound; import flash.media.SoundChannel; import flash.media.SoundMixer; import flash.net.URLRequest; import flash.text.TextField; import flash.utils.ByteArray; import org.papervision3d.materials.WireframeMaterial; import org.papervision3d.objects.primitives.Plane; public class StereoPlane extends Basic3D { private var _doRender:Boolean = false; private var ba:ByteArray = new ByteArray(); private var a:Number = 0; private var multiple:Number =24; private var w:int =1; private var plane1:Plane; private var plane2:Plane; private var _play:MovieClip; private var channel:SoundChannel; public function StereoPlane() { config(); } private function config():void { _play = new MovieClip(); var _text:TextField = new TextField() _text.text = "click here to play"; _text.autoSize = "left"; _text.selectable = _text.mouseEnabled = false; _text.textColor = 0xFFFFFF; _play.addChild(_text); addChild(_play); _play.x = 400/2 - _play.width/2; _play.y = 300/2 - _play.height/2; _play.graphics.beginFill(0x334455); _play.graphics.drawRect(0,0, _text.width, _text.height); _play.graphics.endFill() _play.buttonMode = true; _play.addEventListener(MouseEvent.CLICK, doPlay); } private function doPlay(e:MouseEvent):void { removeChild(_play); //loads music var snd:Sound = new Sound(); snd.load(new URLRequest("http://blog.andrevenancio.com/mp3/sample.mp3")); snd.addEventListener(Event.COMPLETE, doMagic); } public function reStart(e:Event):void { scene.removeChild(plane1); scene.removeChild(plane2); config(); } private function doMagic(e:Event):void { _doRender = true; var music:Sound = e.target as Sound; channel = music.play(); channel.addEventListener(Event.SOUND_COMPLETE, reStart); var mat:WireframeMaterial = new WireframeMaterial(); mat.doubleSided=true; plane1 = new Plane(mat, 600, 600, 15, 15); plane1.rotationX =70; plane1.rotationY =30; var mat2:WireframeMaterial = new WireframeMaterial(); mat2.doubleSided=true; plane2 = new Plane(mat2, 600, 600, 15, 15); plane2.rotationX =-70; plane2.rotationY = 30 + 180; scene.addChild(plane1) scene.addChild(plane2) //new camera settings camera.target = plane1; camera.x =-221; camera.y =70; } override public function render(e:Event):void { if(_doRender) { if(plane1 != null && plane2 != null) { SoundMixer.computeSpectrum(ba, true, 0); for (var i:int = 0; i< 150; i+=w) { if (i<30) { a = ba.readFloat()*multiple*(multiple/8); plane1.geometry.vertices[i].z = -a; plane2.geometry.vertices[i].z = a; } else if (i>=30 && i<150) { a = ba.readFloat()*multiple*(multiple/4); plane1.geometry.vertices[i].z = -a; plane2.geometry.vertices[i].z = a; } } } renderer.renderScene(scene, camera, viewport); } } } }
You can download the source here.
It´s very common to find tutorials about Papervision3D on the web. While I was learning the basics of Papervision3D I had some problems to adjust to the X, Y and Z coordinates because I was used to use TL (Top Left) alignment. So, I decided to add some visual aid to understand what was going on.
Here we have a basic pv3d Class. We have all we need ViewPort3D, BasicRenderEngine, Scene3D and Camera3D.
But with all this, we still cant´t see what´s going on, so we are going to create a basic axis system using Lines3D.
package { import flash.display.Sprite; import flash.events.Event; import org.papervision3d.cameras.Camera3D; import org.papervision3d.core.geom.Lines3D; import org.papervision3d.core.geom.renderables.Line3D; import org.papervision3d.core.geom.renderables.Vertex3D; import org.papervision3d.materials.special.LineMaterial; import org.papervision3d.objects.DisplayObject3D; import org.papervision3d.render.BasicRenderEngine; import org.papervision3d.scenes.Scene3D; import org.papervision3d.view.Viewport3D; public class SimplePV3D extends Sprite { //basic papervision vars private var viewport:Viewport3D; private var renderer:BasicRenderEngine; private var scene:Scene3D; private var camera:Camera3D; public function SimplePV3D() { addEventListener(Event.ADDED_TO_STAGE, startPV3D, false, 0, true); } private function startPV3D(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, startPV3D); viewport = new Viewport3D(400, 300, true, true); renderer = new BasicRenderEngine(); scene = new Scene3D(); camera = new Camera3D(); addChild(viewport); addEventListener(Event.ENTER_FRAME, render); //create createAxis(); } private function createAxis():void { var defaultMaterial:LineMaterial = new LineMaterial(0xFFFFFF); var axes:Lines3D = new Lines3D(defaultMaterial); var xAxisMaterial:LineMaterial = new LineMaterial(0xFF0000); var yAxisMaterial:LineMaterial = new LineMaterial(0x00FF00); var zAxisMaterial:LineMaterial = new LineMaterial(0x0000FF); var origin:Vertex3D = new Vertex3D(0,0,0); var xAxis:Line3D = new Line3D(axes,xAxisMaterial,2,origin,new Vertex3D(100,0,0)); var yAxis:Line3D = new Line3D(axes,yAxisMaterial,2,origin,new Vertex3D(0,100,0)); var zAxis:Line3D = new Line3D(axes,zAxisMaterial,2,origin,new Vertex3D(0,0,100)); axes.addLine(xAxis); axes.addLine(yAxis); axes.addLine(zAxis); scene.addChild(axes); camera.target = axes; } private function render(e:Event):void { //change camera on mouse move var dx:Number=mouseX - 400 * .5; var dy:Number=mouseY - 400 * .5; camera.x+= dx - camera.x * .5; camera.y+= dy - camera.y * .5; renderer.renderScene(scene, camera, viewport); } } }
You can download the source here.



