SoundMixer.computeSpectrum()
I was playing around with ActionScript 3.0's new SoundMixer.computeSpectrum() method last night, and managed to build this simple example:
var url:String = "http://www.helpexamples.com/flash/sound/song3.mp3";
var request:URLRequest = new URLRequest(url);
var s:Sound = new Sound();
s.addEventListener(Event.COMPLETE, completeHandler);
s.load(request);
var song:SoundChannel = s.play();
song.addEventListener(Event.SOUND_COMPLETE, soundCompleteHandler);
var ba:ByteArray = new ByteArray();
var gr:Sprite = new Sprite();
gr.x = 20;
gr.y = 200;
addChild(gr);
var time:Timer = new Timer(50);
time.addEventListener(TimerEvent.TIMER, timerHandler);
time.start();
function completeHandler(event:Event):void {
event.target.play();
}
function soundCompleteHandler(event:Event):void {
time.stop();
}
function timerHandler(event:TimerEvent):void {
SoundMixer.computeSpectrum(ba, true);
var i:int;
gr.graphics.clear();
gr.graphics.lineStyle(0, 0xFF0000);
gr.graphics.beginFill(0xFF0000);
gr.graphics.moveTo(0, 0);
var w:uint = 2;
for (i=0; i<512; i+=w) {
var t:Number = ba.readFloat();
var n:Number = (t * 100);
gr.graphics.drawRect(i, 0, w, -n);
}
}
(** Sorry about the lack of formatting. I'm working on getting the blog restyled, and adding a bunch new AS2 and AS3 examples.)
UPDATE: Tom Green wrote a great article on the SoundMixer class over on Community MX, check it out: Sound Visualization in Flash CS3.
Comments
Note: This example was built using the "Flash Professional 9 ActionScript 3.0 Preview" (http://labs.adobe.com/technologies/flash9as3preview/).
I'll try and modify the example soon and port the code to an external ActionScript file so it can be used as an ActionScript Project in Flex Builder 2, or as a Document class in the Flash Preview.
Posted by: peter | June 28, 2006 08:02 AM
This is great. I took the code and loaded my own mp3. I'm trying to set the defaul volume. It seems like it has been moved to the SoundTransform class, but I can get it to work. Any ideas?
Posted by: Lou Barber | July 1, 2006 02:13 PM
I really like the above script. I'm really into Sound and excited about the possibilites with Flash Player 9. I revised to code and placed it into a package. I also added volume, (figured it out) and also added some other things.
---------
package{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.*;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundMixer;
import flash.media.SoundTransform;
import flash.net.URLRequest;
import flash.utils.Timer;
import flash.geom.Matrix;
import flash.utils.ByteArray;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
public class SoundSpectrum extends Sprite{
private var url:String = "elsewhere.mp3";
private var soundFactory:Sound;
private var channel:SoundChannel;
private var gr:Sprite
private var time:Timer;
private var ba:ByteArray = new ByteArray();
private var positionTimer:Timer;
private var myText:TextField;
public function SoundSpectrum(){
myText = new TextField();
myText.autoSize = TextFieldAutoSize.LEFT;
var format:TextFormat = new TextFormat();
format.font = "Verdana";
format.color = 0xFF0000;
format.size = 10;
myText.defaultTextFormat = format;
addChild(myText);
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
var request:URLRequest = new URLRequest(url);
soundFactory = new Sound();
soundFactory.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
soundFactory.load(request);
channel = soundFactory.play();
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
gr = new Sprite();
gr.x = 20;
gr.y = 400;
addChild(gr);
time = new Timer(50);
time.addEventListener(TimerEvent.TIMER, timerHandler);
time.start();
}
private function ioErrorHandler(event:Event):void {
trace("ioErrorHandler: " + event);
}
private function setVolume(volume:Number):void {
var transform:SoundTransform = channel.soundTransform;
transform.volume = volume;
channel.soundTransform = transform;
}
private function mouseMoveHandler(event:MouseEvent):void {
var yPos:uint = event.stageY;
var volume:Number = 1 - (yPos / stage.stageHeight);
myText.text = "Volume: " + Math.round(volume * 100);
setVolume(volume);
}
private function timerHandler(event:TimerEvent):void {
SoundMixer.computeSpectrum(ba,true);
var fillType:String = "linear";
var colors:Array = [0xFF3300, 0xFFFF00,0x009900];
var alphas:Array = [1, 1, 1];
var ratios:Array = [0, 125, 255];
var matr:Matrix = new Matrix();
matr.createGradientBox(600, 600, 0, 0, 0);
var spreadMethod:String = "PAD";
var i:int;
gr.graphics.clear();
gr.graphics.lineStyle(.5, 0x000000,1,false,"normal","round","miter",8);
gr.graphics.beginGradientFill(fillType, colors, alphas, ratios, matr, spreadMethod);
gr.graphics.moveTo(0, 0);
var w:uint = 10;
for (i=0; i var t:Number = ba.readFloat();
var n:Number = (t * 300);
gr.graphics.drawRect(i, 0, w, -n);
}
}
}
}
Posted by: Lou Barber | July 2, 2006 02:42 PM
Hey, thanks for this example! I used the code to get started and was able to have lots of fun with the sound spectrum!
Lour Barber, I'm not sure about your code tho, I dont know anything about packages. is that for flash 9 alpha? o_O
Posted by: Solomon | August 26, 2006 05:53 PM
COOOOOL!
You should see it rendering Alanis Morissette's 21 things I want in a lover :D
Thanks a lot, Peter ;)
Posted by: Alexandre Madurell | May 23, 2007 05:18 AM
Lou,
An alternative to your setVolume method.
private function setVolume(volume:Number):void {
var transform:SoundTransform = new SoundTransForm(volume)
channel.soundTransform = transform;
}
dont need to get the sound trans form object from the channel first. You can pass the volume to the constructor of the soun transform object. (though thinking about it this does mean creating another object which has an overhead)
Posted by: Matt Poole | May 24, 2007 09:28 AM
Typo in last for loop ?...
Posted by: Daniel Summers | May 30, 2007 12:48 PM
Hello,
This might sound pretty stupid, but can anybody put here a sample on own to load the package into a Flash AS3 file?
Thank you very much in advance and congrats for the excellent demo!
Regards,
Adam.
Posted by: Adam | June 1, 2007 12:08 PM
Nice, can you tell me how I would get the bars to play downwards?
Posted by: Kevin | June 14, 2007 02:19 PM
Never mind :P
var n:Number = (t * -200);
Posted by: Kevin | June 14, 2007 02:21 PM
I have explored the ComputeSpectrum in the below link
http://www.esnips.com/web/TheF3
Posted by: Devaraj | July 3, 2007 03:03 AM
WoW It is great
I am a beginner
I Understood so simply
Thank You very much
Posted by: chiranjeevi | July 13, 2007 06:13 AM
Another great article by Tom Green can be found over at Community MX: http://www.communitymx.com/content/article.cfm?cid=8E9A0
A great article and definitely worth the read.
Posted by: Peter | July 13, 2007 09:23 AM
This is awesome. I have one question though. I am really new to scripting. I used SoundMixer.computeSpectrum and it works fine. Question is this that If i create any graphics or Movie clip etc. It hides the sound spectrum(etc).
What can we do to bring it forward and send all the rest, graphic anim etc in the back ?
Posted by: irfan | August 8, 2007 03:14 PM
Thanks for the post!
From what I surmise, the readFloat() method keeps an internal counter and just gets the next value in the byte array each time you call it.
If your stretch factor (third parameter in the computeSpectrum) is 0 (the default) and your FFTMode is true to turn on the frequency analysis then you have 256 numbers in your bit array representing the frequency analysis for the left channel followed by 256 numbers representing the frequency analysis for the right channel.
--- aside
I think that if your stretch factor is set to 1 then you only get 128 numbers for your left and 128 for your right - and then 56 for each channel if your factor is 2, etc. so watch that! I could be wrong though ;-) (If I am right, then this is a little counter-intuitive!)
--- /aside
When you set w=2 for your loop, you are actually just getting the first 256 numbers in the byte array and thus really just getting the frequency for the left channel - which is probably adequate as most people will not notice.
BUT... if you set w=10 thinking that you are just making fatter bars that average over the expected frequencies (like in that longer example) then you are only getting the first 51 or so (512/10) entries in the bitmap array which is really only the bass frequencies for the left channel. That is why your output looks too bass sloped and no longer has the same distribution!
So a better solution would be to readFloat() for every iteration of the 512 bitmap array and average the first channel's 256 elements with the second channel's 256 elements. And if you want fatter bars then use a modulus to display the data only so often like below. Now you can safely use w to get fatter bars and keep the distribution. Just watch... if you use a stretch factor you will have to change the 256 in this loop (I think - see the aside above).
Also, the timer does not pick up all the beats with its 50 ms delay. You might want to just process it as fast as possible and that is the frame rate so use an onEnterFrame. (Timers and intervals can only fire at the nearest framerate anyway). The onEnterFrame worked better for me:
In your constructor you could add this and get rid of your timer stuff:
addEventListener(Event.ENTER_FRAME, processFrequency);
Then adjust your old timer method to be called this - note the different dataType for the parameter of just Event:
private function processFrequency(event:Event):void {
Posted by: Dan Zen | August 26, 2007 08:14 PM
Sorry... just realized that the code does not average over the w frequencies so here is a fix that will do that:
Posted by: Dan Zen | August 26, 2007 08:28 PM
Dan Zen,
Thanks for the great comments/corrections/improvements. Yeah, I haven't played too closely with the computeSpectrum() method, and I threw this example together [too] quickly, but it definitely looks like my example could use a bit of "improvement".
Peter
Posted by: Peter | August 27, 2007 09:50 AM
Would you know how to use computespectrum to generate an image that represents the whole waveform of the mp3, like what you see if you open up a music file in an audio editor?
Posted by: Jack Freudenheim | November 12, 2007 12:49 PM
I have been trying to create a visualization of an mp3, prior to playing the sound so that I can use the graphics in an editor. I have been attempting to pre-process the sample by repeatedly playing the sound from different positions and sampling the leftPeak with no success. Any ideas?
Posted by: Daniel Jackson | May 14, 2008 04:53 AM