AudioContext技術和音樂可視化(1) 原 薦

寫在最前,測試博客在這裏,直接欣賞完成可視化效果。代碼不日在github公開,性能目前巨爛,RadialGradient損耗巨大,優化正在提上日程。

轉載註明來源。

扒掉網頁上js的煩請留下js裏的頂端註釋謝謝。。雖然我代碼是寫的挺爛的。如果轉發到別的地方了能註明一下作者和來源的話我會很開心的。

https://th-zxj.club 這是你從未體驗過的船新版本

Intro

因爲自己搭了個博客,一時興起,就想寫個動態的博客背景。畢竟用django後端渲染,前端只有jquery和bootstrap已經夠low了,雖說極簡風格也很棒,但是多少有點亮眼的東西纔好和別人吹牛不是嗎。

爲了方便講解,整個思路分爲兩個部分:音樂播放和背景繪製。

一、音樂播放

1.1 AudioContext

概述部分懶得自己寫,參考MDN的描述。

AudioContext接口表示由音頻模塊連接而成的音頻處理圖,每個模塊對應一個AudioNodeAudioContext可以控制它所包含的節點的創建,以及音頻處理、解碼操作的執行。做任何事情之前都要先創建AudioContext對象,因爲一切都發生在這個環境之中。

1.2 瀏覽器支持狀況

AudioContext標準目前還是草案,不過新chrome已經實現了。我使用的chrome版本如下。

版本 70.0.3538.77(正式版本) (64 位)

如果發現console報錯或者其他問題請檢查瀏覽器版本,所有支持的瀏覽器可以在這個鏈接查看。

1.3 AudioContext和音頻處理圖

關於AudioContext我的瞭解不是很深入,所以只在需要用到的部分進行概述。

首先,關於音頻處理圖的概念。

這個名詞不甚直觀,我用過虛幻,所以用虛幻的Blueprint來類比理解。音頻處理圖,其實是一系列音頻處理的模塊,連接構成一張數據結構中的“圖”,從一般使用的角度來講,一個播放音頻的圖,就是AudioSource -> AudioContext.destination,兩個節點構成的圖。其中有很多特殊的節點可以對音頻進行處理,比如音頻增益節點GainNode

對於音頻處理的部分介紹就到這裏爲止,畢竟真的瞭解不多,不過從MDN的文檔看,可用的處理節點還是非常多的,就等標準制訂完成了。

1.4 加載音頻文件並播放

音頻文件加載使用典型的JavaScript接口FileReader實現。

一個非常簡單的實例是這樣

首先是html裏寫上input

<html>
    <body>
        <input type="file" accept="audio/*" onchange="onInputChange">
    </body>
</html>

然後在javascript裏讀文件內容。

function onInputChange(files){
    const reader = new FileReader();
	reader.onload = (event) => {
   		// event.target.result 就是我們的文件內容了
	}
	reader.readAsArrayBuffer(files[0])
}

文件讀取就是這麼簡單,所以回到那個問題:說了那麼多,音樂到底怎麼放?

答案是用AudioContextdecodeAudioData方法。

所以從上面的js裏做少許修改——

// 創建一個新的 AudioContext
const ctx = new AudioContext();

function onInputChange(files){
    const reader = new FileReader();
	reader.onload = (event) => {
   		// event.target.result 就是我們的文件內容了
        // 解碼它
        ctx.decodeAudioData(event.target.result).then(decoded => {
            // 解碼後的音頻數據作爲音頻源
            const audioBufferSourceNode = ctx.createBufferSource();
            audioBufferSourceNode.buffer = decoded;
            // 把音源 node 和輸出 node 連接,boom——
            audioBufferSourceNode.connect(ctx.destination);
            audioBufferSourceNode.start(0);
            // 收工。
        });
	}
	reader.readAsArrayBuffer(files[0])
}

1.5 分析頻譜

頻譜的概念我建議搜一下傅里葉變換,關於時域和頻域轉換的計算過程和數學原理直接略(因爲不懂),至今我還只理解到時域和頻域的概念以及傅里葉變換的實現接受採樣返回採樣數一半長的頻域數據......

不班門弄斧了。

以前寫python的時候用的numpy來進行傅里葉變換取得頻域數據,現在在瀏覽器上用js着實有些難受。不過幸好,AudioContext直接支持了一個音頻分析的node,叫做AudioAnalyserNode

這個Node處於音源Node和播放輸出Node之間,想象一道數據流,音源Node把離散的採樣數據交給Analyser,Analyser再交給輸出Node。

直接看代碼實例。

// 創建一個新的 AudioContext
const ctx = new AudioContext();
// 解碼後的音頻數據作爲音頻源
// 爲了方便管理,將這些Node都放置在回調函數外部
const audioBufferSourceNode = ctx.createBufferSource();

// 創建音頻分析Node!
const audioAnalyser = ctx.createAnalyser();
// 注意注意!這裏配置傅里葉變換使用的採樣窗口大小!比如說,我們要256個頻域數據,那麼採樣就應該是512。
// 具體對應頻率請自行搜傅里葉變換相關博文。
audioAnalyser.fftSize = 512;

function onInputChange(files){
    const reader = new FileReader();
	reader.onload = (event) => {
   		// event.target.result 就是我們的文件內容了
        // 解碼它
        ctx.decodeAudioData(event.target.result).then(decoded => {
            // 停止原先的音頻源
            audioBufferSourceNode.stop();
            // 先把音頻源Node和Analyser連接。
            audioBufferSourceNode.connect(audioAnalyser);
            // 然後把Analyser和destination連接。
            audioAnalyser.connect(ctx.destination);
            // 修改音頻源數據
            audioBufferSourceNode.buffer = decoded;
            audioBufferSourceNode.start(0);
            // 收工。
        });
	}
	reader.readAsArrayBuffer(files[0])
}

window.requestAnimationFrame(function() {
    // 讀取頻域數據
    const freqData = new Uint8Array(audioAnalyser.frequencyBinCount);
    console.log(freqData);
})

頻域數據是二維的,頻率(數組下標)和能量(下標對應值)。悄悄補一句,數學上應該說是該頻率函數圖像的振幅?

其實獲得了這個頻域數據,繼續畫出我們常見的條狀頻域圖就很容易了。參考我一朋友的博客。misuzu.moe,可以看看效果。

關於AudioContext的介紹先到此爲止,等我找時間繼續寫。

PS:代碼不保證複製粘貼就能運行,領會精神,遇到問題查查文檔。MDN比我這博客詳細多了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章