音谱可视化:Audio API开发

效果展示&介绍

本篇的可视化代码主要是根据MIT的2014年2月15日的开源代码修改而来的
MIT音谱可视化->Github
MIT音谱可视化图:
音谱可视化:Audio API开发_第1张图片 
进行修改的音谱可视化图
音谱可视化:Audio API开发_第2张图片 

MIT的音频其实是从通过input标签来获取的,存入缓存中.但是其实我们用的比较多的是html5中的Audio标签,所以将其修改过后,便可对audio的src资源进行读取,并且可以用audio的控制按钮进行音频流的控制.

音谱可视化:Audio API开发_第3张图片 

分析过程

要想做到音谱可视化的结果,那么我们需要什么?
1.获取音频,那么我们可以用audio直接获取音频资源(当然也可以input获取,本篇不做相关介绍)
2.得到音频的频率大小,我们可以使用web Audio API来获取到数值
3.通过频率数值画出波形线,我们可以用画布(canvas)来描绘图形.

获取音频

我们可以直接在html文档中加入audio标签即可:

audio的更多信息参考W3cschool

得到音频的频率大小

这里需要使用到的就是audio api了.我们可以在MSD了解到audio api 的使用方法(网址:https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Audio_API)
在这里我们做简要的介绍:首先创建audioContext对象->需考虑浏览器不同厂商的兼容问题.然后从audio标签中获得媒体资源,并通过audioContext对象的创造媒体节点的函数创造节点.将媒体节点连接到分析音频的处理上,最后输出(audioContext.deestination).我们在分析音频的这个节点上对音频的频率再做提取,并将其可视化.

画出可视化图形

很明显这里需要用到的就是canvas标签和其后的javascript的相关知识.在这里我们描绘的是曲线所以要了解如何用canvas描绘巴塞尔曲线
音谱可视化:Audio API开发_第4张图片
绘制一条二次巴塞尔曲线

代码呈现

在编写代码时,参考MIT的开源代码,我们可以创建一个Visualizer对象来实现我们的功能:


var Visualizer = function()
{
    this.audioContext = null;//音频上下文
    this.source = null;//音频资源
    this.animationId = null;//动画ID
    this.status = 0;//标志来判断是否在播放
    this.forceStop = false;
}

Visualizer.prototype =
{
    ini: function()//初始化函数
    {
        this._preparetionAPI();
    },

    _preparetionAPI: function()//准备Audio API
    {
        //针对不同浏览器厂商
        window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext;
        window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame;
        window.cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.msCancelAnimationFrame;
        //requestAnimationFrame->请求动画

        //异常处理,若创建audioContext对象出错则警告其error
        try
        {
            this.audioContext = new AudioContext();
        }catch( e ){
            alert( e );
            console.log(e);
        }
        //调用开始函数
        this._start();
    },

    _start:function()
    {
        var audioContext = this.audioContext;

        if( audioContext === null )
        {
            return ;
        }
        //开始可视化
        this._visualize(audioContext);
    },

    _visualize:function(audioContext)
    {

        var myAudio = document.getElementById("audio");//获取音频资源
        var audioSourceNode = audioContext.createMediaElementSource(myAudio);//生成了媒体资源节点
        var gainNode = audioContext.createGain();//创建Gain节点
        var analyser = audioContext.createAnalyser();//创造分析节点
        var that = this;
        audioSourceNode.connect(gainNode);
        gainNode.connect(analyser);
        analyser.connect(audioContext.destination);//节点间的连接操作
        if (!audioSourceNode.start) {
            audioSourceNode.start = audioSourceNode.noteOn //in old browsers use noteOn method
            audioSourceNode.stop = audioSourceNode.noteOff //in old browsers use noteOff method
        };

        if( this.animationId !== null )
        {
            cancelAnimationFrame( this.animationId );//针对变换曲目的重新可视化的操作,取消上一曲目的可视化
        }

        if( this.source !== null )
        {
            this.source.stop(0);
        }

        this.status = 1;
        this.source = audioSourceNode;

        audioSourceNode.onended =function()
        {
            that._audioEnd(that);//结束操作
        };
        this._drawQuadratic(analyser);//开始描绘
    },

    _drawQuadratic: function( analyser )
    {
        var that = this;
        var canvas = document.getElementById("canvas");//获取画布对象
        var cwidth = canvas.width;
        var cheight = canvas.height;//获取画布的长和宽
        var meterNum = 110;//波形图的波数
        var ctx = canvas.getContext('2d'),
            gradient = ctx.createLinearGradient(0,0,0,300);
        gradient.addColorStop(1, "#bd4cce");
        gradient.addColorStop(0.6, '#005c58');
        gradient.addColorStop(0, '#6e00ff');//创建渐变style

        var drawMeter =function()//单次的描绘函数
        {
            var array = new Uint8Array( analyser.frequencyBinCount );

            analyser.getByteFrequencyData(array);//获取频率大小,并集合为一个数组


            if( !that.status )
            {
                cancelAnimationFrame( that.animationId );//判断是否终止动画
                return;
            }
            var step = Math.round( array.length / meterNum );//根据我们需要的波数设置跨度
            ctx.clearRect(0, 0, cwidth, cheight );//每次调用的清屏操作

            var field_ini = 5;//设置每个波的半个周期
            var sum_field = 0;//总的周期
            var value_low = 1;//降低波的频率大小,以适应画布的大小


            for( var i = 0; i < meterNum; i++ )//开始描绘
            {
                var value = array[array.length - (i+1) * step ];//获取频率,这里是从低频开始获取
                value /= value_low;//降低频率值
                if( i >= meterNum - 2 )//最后两个波不显示为直线,为了好看
                {
                    value = 0;
                }
                if( i % 2 == 0 )//震荡效果
                {
                    value = -value;
                }

                ctx.beginPath();
                ctx.strokeStyle = gradient; //set the strokeStyle to gradient for a better look
                //一下四步操作是为了描绘一个波的操作,这里不做详解,自行研究
                ctx.moveTo(sum_field, cheight / 2 );
                ctx.quadraticCurveTo(sum_field + 2 * ( field_ini / 6 ), cheight / 2 + value , sum_field + field_ini / 2, cheight / 2 + value);
                ctx.moveTo( sum_field + field_ini, cheight / 2);
                ctx.quadraticCurveTo(sum_field + 4 * ( field_ini / 6 ), cheight / 2 + value , sum_field + field_ini / 2, cheight / 2 + value);

                sum_field += field_ini;
                field_ini += 0;
                if( i < 10 )
                {
                    value += 0.05;
                }
                else if( i > 10 && i < 45 )
                {
                    value_low += 0.5;    
                }
                else if( i > 80 )
                {
                    value_low -= 0.55;  
                }
                ctx.stroke();

            }
            //获取动画ID
            that.animationId = requestAnimationFrame(drawMeter);
        }
        //调用函数,并获取动画ID
        this.animationId = requestAnimationFrame(drawMeter);
    },

    _audioEnd: function()
    {
        if( this.forceStop )
        {
            this.forceStop = false;
            this.status = 1;
            return ;
        };
        this.status = 0;
    }
}

当然我们需要在页面加载的时候就生成这个对象,所以:

window.onload = function()
{
    new Visualizer().ini();
}

你可能感兴趣的:(网页前端)