HTML5音乐可视化

单纯的音乐播放是否过于单调,在听音乐的同时如果也能看见音乐是否更加带感。本课程将带领你使用webAudio和canvas将你的音乐以你喜欢的形式可视化出来,让你的音乐动起来。

应用介绍:

利用webAudio,canvas,CSS3制作的自适应的音乐可视化应用,移动端、pc端通用
在线浏览:地址
源码下载;Github
应用核心结构介绍:
1

难点:
使用webAudio操作分析音频数据
使用Canvas可视化数据

音频获取及播放

构建应用前后端

Node.js搭建的后台,通过npm获取express框架,express就相当于PHP的ThingPHP,Python的Django一样的框架,然后通过npm install安装依赖包后就可以创建应用了,这里用的是ejs的模板引擎,有点类似于PHP中的<? PHPcode ?>,routes就相当于controller,views就是view,程序入口在app.js,这就是个简单的后端应用结构。
步骤:
1、安装Node.js
2、安装express应用生成器:

1
2
npm install -g express-generator //安装express-generator模块
express -e myApp //初始化myApp项目,并增加ejs模板引擎。 '-e':增加ejs模板引擎 (默认模板引擎是jade)

这是,会在myApp目录下生成一系列文件:views/bin/routes/public文件夹,package.json/app.js等文件。
参考:Express应用生成器
3、安装npm依赖包
npm install :将npm依赖包安装到本地文件夹里
4、安装supervisor实时监测模块
npm install -g supervisor :’-g’:表示全局安装
supervisor bin/www :监测应用。
备注:通过supervisor监测应用时,需要node.js出于启用状态

5、启动myApp应用:根据平台选择相应命令
> set DEBUG=myapp & npm start :Window平台使用命令
$ DEBUG=myapp npm start :MacOS 或 Linux平台使用命令
或者通过下面的命令来启动node.js:
./bin/www

这时,在浏览器中打开 http://localhost:3000/ 网址就可以看到这个应用了。

获取服务端音频列表

routes文件夹→ index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var path = require("path");
var media = path.join(__dirname,"../public/media");
/* GET home page. */
router.get('/', function(req, res, next) {
var fs = require("fs");
fs.readdir(media,function(err,names){
if (err) {
console.log(err);
}else{
res.render('index',{title:'Passionate Music',music: names})
}
});

});

views文件夹 → index.ejs

1
2
3
4
5
6
<ul>
<% music.forEach(function(name){ %>
<li><%= name %></li>
<% }) %>

</ul>

ajax请求服务端音频资源数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function $(s){
return document.querySelectorAll(s);
}
var lis = $("#list li");
for (var i = 0; i < lis.length; i++) {
lis[i].onclick =function(){
for (var j= 0; j < lis.length; j++) {
lis[j].className="";
}
this.className="selected";
load("/media/"+this.title);
}
}

var xhr = new XMLHttpRequest();
function load(url){
xhr.open("GET",url);
xhr.responseType = "arraybuffer";
xhr.onload = function(){
console.log(xhr.response);
}
xhr.send();
}

解码并播放音频资源

为音频解码、获取数据等操作,需要用到WebAudio.

AudioContext

AudioContext包含各个AudioNode对象以及它们的联系的对象,可以理解为audio上下文对象。绝大多数情况下,一个document中只有一个AudioContext。
创建:var ac=new window.AudioContext();

AudioContext对象属性:

destination, AudioDestinationNode对象, 所以音频的输出聚集地
currentTime, AudioContext从创建开始到当前的时间(秒)

AudioContext对象方法(函数):

decodeAudioData(被解码的buffer, 成功回调函数succ(buffer), 失败回调函数err); //异步解码
createBufferSource(); //创建AudioBufferSourceNode对象
createAnalyser(); //创建AnalyserNode对象
createGain() / createGainNode(); //创建GainNode对象

AudioBufferSourceNode

表示内存中的一段音频资源,其音频数据存在于AudioBuffer中(其buffer属性)
创建: var buffersource = ac.createBufferSource();
AudioBufferSourceNode对象属性:
buffer, AudioBuffer对象, 要播放的资源数据, 子属性:duration, 该资源时长(秒)
loop, 是否循环播放, 默认false
onended, 播放完毕事件

AudioBufferSourceNode对象方法:

start/noteOn(when=ac.currentTime, offset=0, duration=buffer.duration-offset);when:何时开始播放;offset:从音频的第几秒开始播放;duration:播放几秒
stop/noteOff(when=ac.currentTime),结束播放音频

应用中webAudio API关系图

p1

兼容性问题解决:
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var xhr = new XMLHttpRequest();
var ac = new(window.AudioContext||window.webkitAudioContext)();

function load(url){
xhr.open("GET",url);
xhr.responseType = "arraybuffer";
xhr.onload = function(){
ac.decodeAudioData(xhr.response,function(buffer){
var bufferSource = ac.createBufferSource();
bufferSource.buffer=buffer;
bufferSource.connect(ac.destination);
bufferSource[bufferSource.start?"start":"noteOn"](0);
},function(err){
console.log(err);
});
}
xhr.send();
}

添加音量控制

GainNode:

改变音频音量的对象,会改变通过它的音频数据所有的sample frame的信号强度。
创建:var gainNode = ac.createGain()/ac.createGainNode();
GainNode对象属性:
gain, AudioParam对象, 通过改变其value值可以改变音频信号的强弱,, 默认的value属性值为1,通常最小值为0,最大值为1,其value值也可以大于1,小于0.

1
2
3
4
5
6
7
8
9
10
11
var gainNode =ac.createGain() || ac.createGainNode();
gainNode.connect(ac.destination);

function changeVolume(percent){
gainNode.gain.value = percent*percent;
}
$("#volume")[0].onchange = function(){
changeVolume(this.value/this.max);
}

$("#volume")[0].onchange();

播放bug修复

  1. 点击下一首歌曲时,仍会同时播放上一首歌曲。
  2. 点击一首歌曲(ajax尚未加载完毕),然后又点击下一首歌曲,这时会两首歌曲同时播放。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    var source =null;

    var count=0;
    function load(url){
    var n=++count;
    source && source[source.stop?"stop":"noteOff"](); //加载下一首歌时,如已存在歌曲,则停止。
    xhr.abort(); //点击下一首歌时,终止上一次连接。
    xhr.open("GET",url);
    xhr.responseType = "arraybuffer";
    xhr.onload = function(){
    if (n!=count) return;
    ac.decodeAudioData(xhr.response,function(buffer){
    if (n!=count) return;
    var bufferSource = ac.createBufferSource();
    bufferSource.buffer=buffer;
    bufferSource.connect(gainNode);
    bufferSource[bufferSource.start?"start":"noteOn"](0);
    source=bufferSource;
    },function(err){
    console.log(err);
    });
    }
    xhr.send();
    }

音频可视化

分析音频资源

AnalyserNode:

音频分析对象,它能实时的分析音频资源的频域和时域信息,但不会对音频流做任何处理。
创建: var analyser = ac.createAnalyser();

AnalyserNode对象属性和方法:

fftSize, 设置FFT(FFT是离散傅立叶变换的快速算法,用于将一个信号变换到频域)值的大小,用于分析得到频域 为32-2048之间2的整数次方, 默认为2048, 实时得到的音频频域的数据个数为fftSize的一半。这里相当于分析的精度
frequencyBinCount, 实时得到的频域数据, 为fftSize的一半
getByteFrequencyData(Uint8Array); //复制当前频域数据到Uint8Array(8bit unsigend int array,8位无符号整形类型化数组)中

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
var analyser = ac.createAnalyser(gainNode);
analyser.fftSize=512;
analyser.connect()
......
function load(url){
xhr.onload = function(){
if (n!=count) return;
ac.decodeAudioData(xhr.response,function(buffer){
if (n!=count) return;
var bufferSource = ac.createBufferSource();
bufferSource.buffer=buffer;
bufferSource.connect(analyser);
......
}
function visualizer(){
var arr = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(arr);
requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame||
window.mozRequestAnimationFrame;
function v(){
analyser.getByteFrequencyData(arr);
requestAnimationFrame(v);
}
requestAnimationFrame(v);
}
visualizer();

利用Canvas将音乐数据可视化

柱状图

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
var box=$("#box")[0];
var height,width;
var canvas=document.createElement("canvas");
var ctx=canvas.getContext("2d");
box.appendChild(canvas);

function resize(){
height=box.clientHeight;
width=box.clientWidth;
canvas.height=height;
canvas.width=width;
var line=ctx.createLinearGradient(0,0,0,height);
line.addColorStop(0,'red');
line.addColorStop(0.5,'yellow');
line.addColorStop(1,'green');
ctx.fillStyle=line;
}
resize();
window.onresize=resize;
function draw(arr){
ctx.clearRect(0,0,width,height);
var w=width/size;
for (var i = 0; i < size; i++) {
var h=arr[i]/256*height;
ctx.fillRect(w*i,height-h,w*0.6,h);
}
}
...
function visualizer(){
<!-- var arr = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(arr);
requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame||
window.mozRequestAnimationFrame;
function v(){
analyser.getByteFrequencyData(arr); -->

draw(arr);
<!-- requestAnimationFrame(v);
}
requestAnimationFrame(v); -->

}

圆点图

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
var Dots=[];
function random(m,n){
return Math.round(Math.random()*(n-m)+m);
}
function getDots(){
Dots=[];
for (var i = 0; i < size; i++) {
var x=random(0,width);
var y=random(0,height);
var color="rgb("+random(0,255)+","+random(0,255)+","+random(0,255)+")";
Dots.push({
x:x,
y:y,
color:color
});
}
}
function draw(arr){
ctx.clearRect(0,0,width,height);
var w=width/size;
ctx.fillStyle=line;
for (var i = 0; i < size; i++) {
if (draw.type=="column") {
var h=arr[i]/256*height;
ctx.fillRect(w*i,height-h,w*0.6,h);
}else if (draw.type=="dot") {
ctx.beginPath();
var o=Dots[i];
var r=arr[i]/256*50;
ctx.arc(o.x,o.y,r,0,Math.PI*2,true);
var g=ctx.createRadialGradient(o.x,o.y,0,o.x,o.y,r);
g.addColorStop(0,"#fff");
g.addColorStop(1,o.color);
ctx.fillStyle=g;
ctx.fill();
}

}
}

应用优化

webAudio API梳理

p1

webAudio核心功能封装为对象

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
function MusicVisualizer(obj){
this.source=null;
this.count=0;
this.analyser=MusicVisualizer.ac.createAnalyser();
this.size=obj.size;
this.analyser.fftSize=this.size*2;

this.gainNode=MusicVisualizer.ac[MusicVisualizer.ac.createGain?"createGain":"createGainNode"]();
this.gainNode.connect(MusicVisualizer.ac.destination);

this.analyser.connect(this.gainNode);

this.xhr=new XMLHttpRequest();

this.visualizer=obj.visualizer;

this.visualize();


}
MusicVisualizer.ac=new(window.AudioContext||window.webkitAudioContext)();
MusicVisualizer.prototype.load=function(url,fun){
this.xhr.abort();
this.xhr.open("get",url);
this.xhr.responseType="arraybuffer";
var self=this;
this.xhr.onload=function(){
fun(self.xhr.response);
}
this.xhr.send();
}
MusicVisualizer.prototype.decode=function(arraybuffer,fun){
MusicVisualizer.ac.decodeAudioData(arraybuffer,function(buffer){
fun(buffer);
},function(err){
console.log(err);
});
}
MusicVisualizer.prototype.play=function(url){
var n=++this.count;
var self=this;
this.source&&this.stop();
this.load(url,function(arraybuffer){
if (n!=self.count) return;
self.decode(arraybuffer,function(buffer){
if (n!=self.count) return;
var bs=MusicVisualizer.ac.createBufferSource();
bs.connect(self.analyser);
bs.buffer=buffer;
bs[bs.start?"start":"noteOn"](0);
self.source=bs;
})
});

}

MusicVisualizer.prototype.stop=function(){
this.source[this.source.stop?"stop":"noteOff"](0);
}
MusicVisualizer.prototype.changeVolume=function(percent){
this.gainNode.gain.value=percent*percent;
}

MusicVisualizer.prototype.visualize=function(){
var arr = new Uint8Array(this.analyser.frequencyBinCount);
this.analyser.getByteFrequencyData(arr);
requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame||
window.mozRequestAnimationFrame;
var self=this;
function v(){
self.analyser.getByteFrequencyData(arr);
self.visualizer(arr);
requestAnimationFrame(v);
}
requestAnimationFrame(v);
}

柱状图美化/圆点图美化

主要是修改draw、getDots函数中颜色、宽、高等数据。

参考资源:

  1. 使用Express框架应用生成器快速搭建一个应用骨架 http://blog.csdn.net/kongjunchao159/article/details/51121482
  2. WEB AUDIO API简易入门教程 http://newhtml.net/web-audio-api%E7%AE%80%E6%98%93%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B/
  3. 通过Web Audio API可视化输出MP3音乐频率波形 http://ourjs.com/detail/54d48406232227083e000029
  4. Web Audio介绍 http://www.cnblogs.com/jimmychange/p/3498911.html
  1. 源码下载;Github
坚持原创技术分享,您的支持将鼓励我继续创作!