寫在最前,測試博客在這裏,直接欣賞完成可視化效果。代碼不日在github公開,性能目前巨爛,RadialGradient損耗巨大,優化正在提上日程。javascript
轉載註明來源。html
扒掉網頁上js的煩請留下js裏的頂端註釋謝謝。。雖然我代碼是寫的挺爛的。若是轉發到別的地方了能註明一下做者和來源的話我會很開心的。前端
https://th-zxj.club 這是你從未體驗過的船新版本java
由於本身搭了個博客,一時興起,就想寫個動態的博客背景。畢竟用django後端渲染,前端只有jquery和bootstrap已經夠low了,雖然說極簡風格也很棒,可是多少有點亮眼的東西纔好和別人吹牛不是嗎。node
爲了方便講解,整個思路分爲兩個部分:音樂播放和背景繪製。python
概述部分懶得本身寫,參考MDN的描述。jquery
AudioContext接口表示由音頻模塊鏈接而成的音頻處理圖,每一個模塊對應一個
AudioNode
。AudioContext能夠控制它所包含的節點的建立,以及音頻處理、解碼操做的執行。作任何事情以前都要先建立AudioContext對象,由於一切都發生在這個環境之中。git
AudioContext標準
目前仍是草案,不過新chrome已經實現了。我使用的chrome版本以下。github
版本 70.0.3538.77(正式版本) (64 位)
若是發現console報錯或者其餘問題請檢查瀏覽器版本,全部支持的瀏覽器能夠在這個連接查看。chrome
關於AudioContext
個人瞭解不是很深刻,因此只在須要用到的部分進行概述。
首先,關於音頻處理圖的概念。
這個名詞不甚直觀,我用過虛幻,因此用虛幻的Blueprint
來類比理解。音頻處理圖,實際上是一系列音頻處理的模塊,鏈接構成一張數據結構中的「圖」,從通常使用的角度來說,一個播放音頻的圖,就是AudioSource -> AudioContext.destination
,兩個節點構成的圖。其中有不少特殊的節點能夠對音頻進行處理,好比音頻增益節點GainNode
。
對於音頻處理的部分介紹就到這裏爲止,畢竟真的瞭解很少,不過從MDN的文檔看,可用的處理節點仍是很是多的,就等標準制訂完成了。
音頻文件加載使用典型的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]) }
文件讀取就是這麼簡單,因此回到那個問題:說了那麼多,音樂到底怎麼放?
答案是用AudioContext
的decodeAudioData
方法。
因此從上面的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]) }
頻譜的概念我建議搜一下傅里葉變換,關於時域和頻域轉換的計算過程和數學原理直接略(由於不懂),至今我還只理解到時域和頻域的概念以及傅里葉變換的實現接受採樣返回採樣數一半長的頻域數據......
不班門弄斧了。
之前寫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比我這博客詳細多了。