# 做一个酷酷的音乐频谱

# 先上个demo

音乐频谱

# AudioAPI的基本概念

H5的Web Audio API可以很方便的使用(虽然各家浏览器实现的标准都不太一样)各种音频功能,比如可以用麦克风录制声音然后生成音频文件,还可以对声源定位,做那种3D音效,混音等等,听过3D音效的都知道,声音一会左,一会右的,还支持实时的声音分析,根据分析的数据可以做一些可视化效果等等。 其中AudioContext是Web Audio API的基石,AudioContext对象可以“线性”处理字节流。

这个线性的意思就是用Web Audio的信号传递途径

1.首先要建立音频的前后境况
2.在境况内设定音频的输入源
3.加入各种音响效果
4.设定音频的输出终点
5.将音频的输入,效果,输出终点连接起来

如图


AudioAPI的大部分操作都是通过一个个连接的节点完成的,这些节点需要连通,从声源节点到输出节点都必须保持通畅才能正常工作。就像在电路上连接一个个元件来完成不同的工作,最后连接到扬声器上。

最简单的路线就是从声源直接连接到输出节点:source → destination

输出节点在AudioContext实例的destination上可以找到,而声源节点却有很多。

MediaElementSource、MediaStreamSource、BufferSource、Oscillator、createScriptProcessor

这些都是声源节点。它们都是可以产生波,并传输给下一个节点的。所以要让AudioAPI发出声音,最简单的代码可以这么写:

var AudioContext = AudioContext||webkitAudioContext;
var context = new AudioContext;
var oscillator = context.createOscillator();
oscillator.connect(context.destination);
oscillator.start(0);
1
2
3
4
5

直接从声源连接到输出节点是最简单的用法,AudioAPI还提供了很多节点用于对音频的特殊处理。

比如Gain节点用来调整音量、BiquadFilter节点用来过滤一些数据。当数据从这些节点上流过时就会被做相应的处理。

这些节点的连接也不是一对一那么简单的,它可能一对多,比如一个声源头可以同时给多个节点处理,之后再把这些中间节点汇总起来,这就要关心这些节点的分支与汇总的问题了。

总之,AudioAPI的设计就像是电路设计,有机地组合它所提供的节点就可以完成几乎所有音频处理。

# 通过HTML Media元素流式加载

使用audio元素流式加载音乐文件, 在JavaScript中调用createMediaElementSource方法, 直接操作HTMLMediaElement。

<!--
	写一个audio标签,给一个音乐地址
	地址必须是本地音乐,不能使用跨域的音乐资源
	运行代码还必须是服务器环境,不然没效果,因为跨域限制
	加上controls(显示控制面板) autoplay(自动播放) loop(循环播放)这3个属性
-->
<audio id="audio" src="1.mp3" autoplay="autoplay" loop="loop" controls="controls"></audio>
1
2
3
4
5
6
7

现在audio标签可以用来播放音乐了。

创建canvas画布

<style type="text/css">
#canvas {
	position: absolute;
	left: 0;
	top: 0;
	z-index: -1;
	background: #000;
}
</style>
<canvas id="canvas"></canvas>
1
2
3
4
5
6
7
8
9
10

编写js脚本

var audio = document.getElementById("audio");
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
// 创建境况
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext();
// 创建输入源
var source = audioContext.createMediaElementSource(audio);
// 用createAnalyser方法,获取音频时间和频率数据,实现数据可视化。
var analyser = audioContext.createAnalyser();
// 连接:source → analyser → destination
source.connect(analyser);
// 声音连接到扬声器
analyser.connect(audioContext.destination);
/* 存储频谱数据,Uint8Array数组创建的时候必须制定长度,
长度就从analyser.frequencyBinCount里面获取,长度是1024*/
var arrData = new Uint8Array(analyser.frequencyBinCount), 
// 能量柱个数,不能大于数组长度1024,没意义
count = Math.min(500,arrData.length), 
/* 计算步长,每隔多少取一个数据用于绘画,意抽取片段数据来反映整体频谱规律,
	乘以0.6是因为,我测试发现数组长度600以后的数据基本都是0了,
	画出来能量柱高度就是0了,为了效果好一点,所以只取前60%,
	如果为了真实可以不乘以0.6
*/
step = Math.round(arrData.length * 0.6 / count),
value = 0, //每个能量柱的值
drawX = 0, //能量柱X轴位置
drawY = 0, //能量柱Y轴坐标
height = canvas.height = window.innerHeight,//canvas高度
width = canvas.width = window.innerWidth,//canvas宽度
// 能量柱宽度,设置线条宽度
lineWidth = context.lineWidth = canvas.width / count;
// 设置线条宽度
context.lineWidth = lineWidth;
// 渲染函数
function render() {
	//每次要清除画布
	context.clearRect(0, 0, width, height);
	//获取频谱值
	analyser.getByteFrequencyData(arrData);
	for(var i = 0; i &lt; count; i++) {
		//前面已经计算好步长了
		value = arrData[i * step + step];
		//X轴位置计算
		drawX = i * lineWidth;
		/*  能量柱的高度,从canvas的底部往上画,那么Y轴坐标就是画布的高度减去能量柱的高度,
			而且经测试发现value正常一般都比较小,要画的能量柱高一点,所以就乘以2,
			又防止太高,取了一下最大值,并且canvas里面尽量避免小数值,取整一下
		*/
		drawY = parseInt(Math.max((height - value * 2), 10));
		//开始一条路径
		context.beginPath();
		/* 设置画笔颜色,hsl通过这个公式出来的是很漂亮的彩虹色
		   H:Hue(色调)。0(或360)表示红色,120表示绿色,240表示蓝色,也可取其他数值来指定颜色。取值为:0 - 360
		   S:Saturation(饱和度)。取值为:0.0% - 100.0%
		   L:Lightness(亮度)。取值为:0.0% - 100.0%
		 */
		context.strokeStyle = "hsl( " + Math.round((i * 360) / count) + ", 100%, 50%)";
		// 从X轴drawX,Y轴就是canvas的高度,也就是canvas的底部
		context.moveTo(drawX, height);
		// 从X轴drawX,Y轴就是计算好的Y轴,是从下往上画,这么理解
		context.lineTo(drawX, drawY);
		/* stroke方法才是真正的绘制方法,顺便也相当于结束了这次的绘画路径,
			就不用调用closePath方法了
		*/
		context.stroke();
	}
	// 用requestAnimationFrame做动画
	requestAnimationFrame(render);
}
//调用render函数
render();
//自适应处理
function resize(){
	height = canvas.height = window.innerHeight;
	width = canvas.width = window.innerWidth;
	// 能量柱宽度,设置线条宽度
	context.lineWidth = lineWidth = canvas.width / count;
}
window.addEventListener("resize",resize,false);
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
78
79
80

# 最后有一个3D版的带搜索功能的Demo

音乐频谱(手动点击页面,自动播放,点击打开歌曲列表,支持回车搜索)