做一个酷酷的音乐频谱

Canvas a'ゞ『完美』 2年前 (2017-05-20) 4712次浏览 已收录 16个评论 扫描二维码

先上个 demo

做一个酷酷的音乐频谱
音乐频谱

AudioAPI 的基本概念

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

首先要建立音频的前后境况(Context)
在境况内设定音频的输入源
加入各种音响效果
设定音频的输出终点
将音频的输入,效果,输出终点连接起来

如图
做一个酷酷的音乐频谱
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);

直接从声源连接到输出节点是最简单的用法,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>

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

创建 canvas 画布

<style type="text/css">
    #canvas {
	position: absolute;
	left: 0;
	top: 0;
	z-index: -1;
	background: #000;
    }
</style>

 

编写 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), 
	count = Math.min(500,arrData.length), //能量柱个数,不能大于数组长度 1024,没意义
	/*计算步长,每隔多少取一个数据用于绘画,意抽取片段数据来反映整体频谱规律,
           乘以 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 < 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);

最后其实我还有一个功能更完善的 demo

音乐频谱(手动点击播放,点击打开按钮,支持回车搜索)
a'ゞ『完美』版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明做一个酷酷的音乐频谱
喜欢 (82)
[]
分享 (0)
a'ゞ『完美』
关于作者:
一个前端小白菜
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(16)个小伙伴在吐槽
  1. 牛逼,在下佩服,这才是我一直追求的音乐播放器频谱效果,顺便求解几个疑问. 邮箱:504590446@qq.com
    Hacksxs2018-02-23 19:55 回复 Windows 7 | Chrome 63.0.3239.132
  2. 牛逼啊,你能成大事。我或许永远也学不会。
    fugel2017-09-22 16:04 回复 Windows 10 | Chrome 61.0.3163.91
  3. hack this Continue ....
    顾航2017-06-02 16:47 回复 Windows 10 | Chrome 58.0.3029.110
  4. hack this Continue ...
    顾航2017-06-02 16:42 回复 Windows 10 | Chrome 58.0.3029.110
  5. hack this Continue
    顾航2017-06-02 16:42 回复 Windows 10 | Chrome 58.0.3029.110
  6. DFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDF
    顾航2017-06-02 16:38 回复 Windows 10 | Chrome 58.0.3029.110
  7. CCCCCCCCCCCCCCCC
    顾航2017-06-02 16:37 回复 Windows 10 | Chrome 58.0.3029.110
  8. dddddddddddddddd
    顾航2017-06-02 16:36 回复 Windows 10 | Chrome 58.0.3029.110
  9. 324234234234
    顾航2017-06-02 16:36 回复 Windows 10 | Chrome 58.0.3029.110
  10. ddd
    ddd2017-06-02 16:31 回复 Windows 10 | Chrome 58.0.3029.110
  11. 厉害,在下佩服 ⊙▂⊙
    kk2017-05-22 18:34 回复 Windows 7 | Chrome 50.0.2661.102
  12. 哇,厉害,求源码学习
    cavin2017-05-22 16:20 回复 Windows 10 | Chrome 57.0.2987.110
    • a'ゞ『完美』
      你说你连个邮箱都不留正确的,我回复了你也看不到啊,醉了,右键不就是源码么。。。。。。
      a'ゞ『完美』2017-05-22 18:00 回复 Windows 10 | Chrome 58.0.3029.110
  13. :?:
    ahjk2017-05-21 22:33 回复 Linux | Chrome 53.0.2785.49
  14. 狗哥厉害 :idea: :wink: :wink:
    2017-05-21 13:15 回复 Linux | Chrome 53.0.2785.49
  15. 狗哥厉害
    2017-05-21 13:13 回复 Linux | Chrome 53.0.2785.49