做一个酷酷的音乐频谱

3,341 阅读5分钟

先上个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发出声音,最简单的代码可以这么写:

  1. 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" controls autoplay loop></audio>

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

创建canvas画布

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

编写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协议进行授权 , 转载请注明做一个酷酷的音乐频谱