AS3声音功能中最令人兴奋的新增功能之一,就是可以访问声音频谱数据.这在以往的版本中是比较难实现的,或者得借助第三方工具才能完成的,而现在,这些频谱功能给内建到SoundMixter类里的computeSpectrum().所以在编写频谱之前,我们先从AS3的帮助文档中来认识一下computeSpectrum()是什么东西.
先浏览一下效果先,点击频谱可以切换效果 (注意,浏览前,关掉或暂停其它FLASH的声音,如本页右侧的音乐播放器,computeSpectrum () 会捕捉到其它FLASH上的声音而导致沙箱安全冲突= =!)
computeSpectrum () 方法
public static function computeSpectrum(outputArray:ByteArray, FFTMode:Boolean = false, stretchFactor:int = 0):void
获取当前声音波形的快照,并将其放在指定的 ByteArray 对象中。 这些值已设置为标准浮点值(范围为 -1.0 到 1.0)格式。新值覆盖了传递到 outputArray 参数的 ByteArray 对象。 创建的 ByteArray 对象的大小固定为 512 个浮点值,其中前 256 个值表示左声道,后 256 个值表示右声道。
参数 outputArray:ByteArray — 用于保存与声音关联的值的 ByteArray 对象。 如果由于安全性限制 (areSoundsInaccessible == true) 而导致任何声音不可用,则 outputArray 对象将保持不变。 如果停止了所有声音,则用零填充 outputArray 对象。
FFTMode:Boolean (default = false) — 一个用于指示是否首先对声音数据执行 Fourier 转换的布尔值。 将此参数设置为 true 会导致方法返回的是频谱而不是原始声音波形。 在频谱中,左侧呈现的是低频,右侧呈现的是高频。
stretchFactor:int (default = 0) — 声音采样的分辨率。 如果将 stretchFactor 值设置为 0,则会按 44.1 KHz 对数据进行采样;如果值为 1,则按 22.05 KHz 对数据进行采样;如果值为 2,则按 11.025 KHz 对数据进行采样;依此类推。
所以我们只要建立一个字节数组,然后不断的通过该方法得到声音快照.然后通过图形运算,形象的描绘出一个阶梯状的频谱图,就是接着我要做的事情.
今天我要做的频谱效果呢,是从千千静听模仿过来的. 如图.
可见,频谱中左侧呈现的是低频,首先要从computeSpectrum ()获得频谱的数据,就必须将第二个参数FFTMode设为true.当我们获得快照以后,字节数组的长度总是为2048,而它储存的是32位单精度浮点数据,也即一个数据占4个字节,2048的长度,实际上就是存了2048/4=512个浮点数据,(经实际试验:它的值在0~1.3之间,没有找到资料有明确介绍最大值的,所以我也只是自己在实际调试中得出来的,未必最大值就是1.3)所以如果我们想要通过ByteArray的position来移动读取指针时,就必须以4的整数倍数为单位来移动.
在512个数据中,前256个是描述左声道的,而后256个是描述右声道的.而在每个声道的靠前的数字是描述低频的,靠后的数字是描述高频的.而接下来我要画的频谱,他并没有512条柱体.而只有64条柱体,所以呢.我的做法就是取左声道的连续的四个浮点数据加上右声道相应位置的四个连续的数据的总和再除于一个平均因数得出来的一个近似值来表示柱体的高度.
整个频谱是由一个一个的柱体来组成的,所以首先我们得先用一个类来定义这个柱体,那么柱体是如何运作的呢,其实很简单,它的主要任务就是只要他的高度不为0,他就不断的缩短,在缩短的过程中,频谱数据会命令他一个新的高度,如果新的高度比它现在要高,他就自动变为指定的高度,如果比现在还要短,则无视命令,然后就接着继续缩短,直到高度为0时就等待新的指令高度.而柱体上的小方块,它的运行方式也跟柱体差不多一样,只要它不在最低点,他就不断的下降(下降的速度比柱体缩短的慢),直到有一个比它所在的高度还要高的指令时它才变为新的高度,另外,它还会不断的与柱体做碰撞检测,永远保证方块是处于柱体之上的.最低也就是放在柱体的上面.以下就是通过这个原理所描述的柱体类
import flash.display.Sprite;
import flash.utils.Timer;
import flash.events.TimerEvent;
class Rect extends Sprite{
private var s:Sprite; //柱状体
private var z:Sprite; //柱状体上的小方块
private var timer:Timer;
private var h:Number;
public function Rect(_width:Number,_height:Number,color:uint = 0xFFFFFF){
s = new Sprite();
//画柱状体
s.graphics.beginFill(color);
s.graphics.drawRect(0,0,_width,_height);
s.graphics.endFill();
//旋转180度柱体才会从上往下降
s.rotation = 180;
s.height = 0;
s.y = _height;
addChild(s);
h = _height;//记录最高长度
var zHeight:Number = 2;//块状体高
z = new Sprite();
z.graphics.beginFill(0x2AEAEB);
z.graphics.drawRect(0,0,_width,zHeight);
z.graphics.endFill();
z.y = _height - zHeight;
z.rotation = 180;
addChild(z);
timer = new Timer(40);
timer.addEventListener(TimerEvent.TIMER,onTimer);
timer.start();
}
private function onTimer(e:TimerEvent):void{
if(s.height > 0){
var speed:Number = 0.02 * h;//柱状体下降的速度
if(speed > s.height)s.height = 0;
else s.height -= speed;
}
if(z.y + z.height >= h - s.height){
z.y = h - s.height - z.height;
}
else{
var zspeed:Number = 0.01 * h;
z.y += zspeed;
}
}
//更新柱体的高度,仅只当设置的高度比当前的高度还要高的时候才更新.
public function update(percent:Number):void{
if(percent>1.0) percent = 1.0;
if(s.height < h * percent){
s.height = h * percent;
if(z.y + z.height >= h - s.height){
z.y = h - s.height - z.height;
}
}
}
}
接下来我们就可以读取频谱数据来画频谱图了:
private var s:Sprite = new Sprite();
private var Main:Sprite ;
private var spectrum:ByteArray = new ByteArray();
private var timer:Timer;
private var m_width:Number;
private var m_height:Number;
public function Spectrum(_width:Number = 200,_height:Number = 100)
{
addChild(s);
m_width = _width;
m_height = _height;
prismatical(_width,_height);
}
private function prismatical(_width:Number,_height:Number):void{
Main = new Sprite();
s.addChild(Main);
for(var i:uint = 0; i<64 ; i++){
var r:Rect = new Rect(5,_height);
r.y = 0;
r.x = 6 * i;
Main.addChild(r);
r.name = "r_" + i;
}
Main.width = _width;
timer = new Timer(200);
if(!SoundMixer.areSoundsInaccessible()){
timer.addEventListener(TimerEvent.TIMER,prismatical_update);
timer.start();
}
}
private function prismatical_update(e:Event):void{
SoundMixer.computeSpectrum(spectrum,true);
for(var i:uint = 0; i < 64; i++){
var a:Number = 0.0;
spectrum.position = i * 16;//将指针移到左声道的相应位置
a = spectrum.readFloat() + spectrum.readFloat() + spectrum.readFloat() + spectrum.readFloat();//读取4个连续的数据
spectrum.position = 1024 + i * 16;//将指针移到右声道的相应位置
a += spectrum.readFloat() + spectrum.readFloat() + spectrum.readFloat() + spectrum.readFloat();//读取4个连续的数据
var r:Rect = Rect(Main.getChildByName("r_" + i));
r.update(a / 6);//更新柱体长度,除以一个平均因数,大家也许可以试着改一下6为其它的数字试试
}
}
至此阶梯频谱便已完成,看图
接着模仿了千千静听上的另外一个波形图.
波形图的效果是我在网上的一个Blog上看到的,所以我就直接拿过来整合在一起了.原理什么的,可以直接上闪客小东's Blog 的更炫的效果来了!AS3波形图教程<二>去看吧.我就直接贴修改过的代码了.
private var Main : Sprite ;
private var spectrum : ByteArray = new ByteArray();
private var timer : Timer;
private function waveform( _width : Number , _height : Number ): void {
var line : Sprite = new Sprite();
var bmpData : BitmapData = new BitmapData( _width , _height , true , 0x000000);
var bmp : Bitmap = new Bitmap( bmpData);
//声明一个BlurFilter滤镜
var blur : BlurFilter = new BlurFilter( 7 , 7 , BitmapFilterQuality . LOW);
var n : Number = 0;
var r : Rectangle = new Rectangle( 0 , 0 , _width , _height);
var p : Point = new Point( 0 , 0);
var colorM : ColorMatrixFilter = new ColorMatrixFilter ([ 0.9 , 0 , 0 , 0 , 0 ,
0 , 0.9 , 0 , 0 , 0 ,
0 , 0 , 0.9 , 0 , 0 ,
0 , 0 , 0 , 0.68 , 0 ,// 0.68 是透明因数 , 数值越小 , 背景模糊效果淡化得越快 , 可以试着设一下其它的值来测试更好的视觉效果
]);
Main = new Sprite();
Main . blendMode = BlendMode . ADD;
s . addChild( Main);
Main . addChild( bmp);
Main . addChild( line);
timer = new Timer( 80);
if (! SoundMixer . areSoundsInaccessible ()){
timer . addEventListener( TimerEvent . TIMER , waveform_update);
timer . start();
}
function waveform_update( e : TimerEvent ): void {
n = 0;
//这里是为了每2次才执行一次滤镜而做的if,如果需要让原来的波形图消失的更慢就把2改成更大的数字
if( timer . currentCount % 2 == 0 ){
//将Main的内容绘制到bmpData
bmpData . draw( Main);
//应用滤镜
bmpData . applyFilter( bmpData , r ,p , colorM);
bmpData . applyFilter( bmpData , r ,p , blur);
}
//清除绘图
line . graphics . clear();
//设置线条样式,颜色湖蓝,宽度1,透明度100
line . graphics . lineStyle( 1 , 0x2 AEAEB , 100);
SoundMixer . computeSpectrum( spectrum);
//左声道
for( var i : uint = 0; i < 256; i += 2 ){
//在ByteArray中读取一个32位的单精度浮点数(这个是livedoc上写的,实际就是把数据流读取成浮点数)
n = spectrum . readFloat();
//为了平滑,隔着读
spectrum . readFloat ();// spectrum . position += 4;
//这个实际作用是把n扩大一下
// n = n*_height;
//如果i不为0
if( i != 0 ){
//画波形图
line . graphics . lineTo( i * _width / 256,_height * (n + 3)/ 6 );
} else {
//移动
line . graphics . moveTo( 0 , _height * (n + 3 )/ 6);
}
}
//右声道
for( i = 0; i < 256; i += 2 ){
n = spectrum . readFloat();
spectrum . readFloat ();// spectrum . position += 4;
if( i != 0 ){
line . graphics . lineTo( i * _width / 256,_height * (n + 3) / 6);
} else {
line . graphics . moveTo( 0 , _height * (n + 3 )/ 6);
}
}
}
}
最后我偷懒,把这所有的代码都整合到一个类文件里面.....所以代码可能看起来有点吃力..
调用的代码:
import flash.display.Sprite;
import flash.media.Sound;
import flash.net.URLRequest;
import net.conanlwl. *;
public class myTest extends Sprite
{
private var s : Spectrum;
public function myTest()
{
var snd : Sound = new Sound( new URLRequest( "http://www.conanlwl.net/demo/Spectrum/v3.mp3"));
snd . play();
s = new Spectrum();
s . addEventListener( MouseEvent . CLICK , onChangeSpectrumStyle);
addChild(s);
}
private function onChangeSpectrumStyle( e : MouseEvent ): void {
s . changeStyle();
}
}
}