根據上面這張圖,我們寫好了以下的程式碼,就成功得到經由傅立葉轉換的音頻訊號了,取得了名為dataArray音頻陣列,耶比~~可以開始圖像化了!
// 創建音訊物件
let AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = new AudioContext();
// 創建節點
let audio = document.querySelector("#Music");
let source = audioCtx.createMediaElementSource(audio);
let gainNode = audioCtx.createGain();
let analyserNode = audioCtx.createAnalyser();
// 連接節點
source.connect(gainNode);
gainNode.connect(analyserNode);
analyserNode.connect(audioCtx.destination);
// 對每個節點進行設定
gainNode.gain.value = 1;
analyserNode.fftSize = 2048;
// 利用analyserNode取得當下的音頻訊號
let bufferLength = analyserNode.frequencyBinCount;
let dataArray = new Uint8Array(bufferLength);
analyserNode.getByteFrequencyData(dataArray);
咦? 你說這樣有點跳太快了嗎
開玩笑的啦,待會就一步一步拆解給大家看唄,今天有不少知識點,先讓大家熟悉今天的六個名詞
名詞有點多對吧!沒關係我們慢慢來,先從最簡單的開始:「如果我今天要在底層實作一個音樂播放器,可以怎麼做?」
// 步驟 1
let audio = document.querySelector("#Music");
// 步驟 2
let AudioContext = window.AudioContext || window.webkitAudioContext; // 跨瀏覽器寫法
let audioCtx = new AudioContext();
let source = audioCtx.createMediaElementSource(audio);
// 步驟 3
source.connect(audioCtx.destination);
如果少了步驟3,你會發現這個ID為#Music的audio元件不能播放了!因為在步驟2的時候,我們已經把它音訊放到了audio當中做處理,必須主動執行步驟3接到音訊出口,才能讓它有音源可以播放。也就是說,你現在是DJ了!可以決定任何一個混音源(節點),最後要不要接上去。
以下是用來製作昨天的Demo時,用到的5個步驟:
// 步驟 1
let AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = new AudioContext();
// 步驟 2
let audio = document.querySelector("#Music");
let source = audioCtx.createMediaElementSource(audio);
let gainNode = audioCtx.createGain();
let analyserNode = audioCtx.createAnalyser();
// 步驟 3
source.connect(gainNode);
gainNode.connect(analyserNode);
analyserNode.connect(audioCtx.destination);
// 步驟 4
gainNode.gain.value = 1;
analyserNode.fftSize = 2048;
// 步驟 5
let bufferLength = analyserNode.frequencyBinCount;
let dataArray = new Uint8Array(bufferLength);
analyserNode.getByteFrequencyData(dataArray);
這樣是不是清楚多了呢?
這個步驟5是相當關鍵的一環,允許你取得音頻並進行分析,先來解釋出現的新東西:
關於傅立葉(微積分),簡單來說,就是你做越多次,效果越好,花的時間越多;做越少次,效果越差(失真),花的時間越少。還是不太理解的朋友沒關係,你就把fftSize設個256也很夠用了!那麼你只需要準備長度為analyserNode.frequencyBinCount(剛好會是128)的陣列去裝訊號就好。若是上面的例子來說,長度會剛好是1024。
講到這聰明的你也應該明白了吧,如果不擔心取得的音訊失真的話,fftSize簡直是越小越好,陣列越短,對於效能的優化當然是越好!而dataArray代表了每個頻段的信號大小,fftSize越小,顯然頻寬越大,也因此才會失真。
這邊還有幾個知識點要補充:
而總頻率又被記錄在一開始的音源audioContext中,預設為48000。這邊就先爆個雷,明天會用到以下程式碼:
let bands = audioCtx.sampleRate / (analyserNode.fftSize / 2); // 每個區段的頻寬
let highestBands = 16000; // 16kHz高音頻以下的音樂
let index = highestBands / bands;
假如我想繪製的頻率信號,最多只到高音頻16kHz,那就用這個去除以陣列每個間隔的頻率寬度bands,就可以得到16kHz對應於dataArray的索引位置index了!
那麼明天我們就可以把拿到手的dataArray圖像化囉!
整理文章真的是一門大學問...光是懂還不行,要講得清楚,安排前後上下文,本來以為分享技術這件事很簡單,沒想到這麼花時間!再次讚嘆網路上,有這麼多無私分享的大神。
該系列文章內文和程式碼皆出自本人撰寫,名詞解釋參考MDN Web Docs文件。