音譜可視化:Audio API開發

效果展示&介紹

本篇的可視化代碼主要是根據MIT的2014年2月15日的開源代碼修改而來的
MIT音譜可視化->Github
MIT音譜可視化圖:
MIT音譜可視化圖 
進行修改的音譜可視化圖
這裏寫圖片描述 

MIT的音頻其實是從通過input標籤來獲取的,存入緩存中.但是其實我們用的比較多的是html5中的Audio標籤,所以將其修改過後,便可對audio的src資源進行讀取,並且可以用audio的控制按鈕進行音頻流的控制.

用audio的音譜可視化 

分析過程

要想做到音譜可視化的結果,那麼我們需要什麼?
1.獲取音頻,那麼我們可以用audio直接獲取音頻資源(當然也可以input獲取,本篇不做相關介紹)
2.得到音頻的頻率大小,我們可以使用web Audio API來獲取到數值
3.通過頻率數值畫出波形線,我們可以用畫布(canvas)來描繪圖形.

獲取音頻

我們可以直接在html文檔中加入audio標籤即可:
<audio src="./mp3/Approaching Nirvana - You.mp3" id="audio"></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描繪巴塞爾曲線
巴塞爾曲線
繪製一條二次巴塞爾曲線

代碼呈現

在編寫代碼時,參考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();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章