iT邦幫忙

2021 iThome 鐵人賽

DAY 4
0
Modern Web

從零開始打造網頁遊戲-造輪子你也辦的到!系列 第 4

Chapter1-DJ最愛的音頻動感圖像(III)媽媽叫你不要玩音樂,現在知道當DJ很難了吧

加油!這章節快完成了

https://ithelp.ithome.com.tw/upload/images/20210911/201351975SuixoaMT9.jpg
根據上面這張圖,我們寫好了以下的程式碼,就成功得到經由傅立葉轉換的音頻訊號了,取得了名為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. 元件HTMLMediaElement
    其實在HTML寫一個audio標籤的時候,就會用到它。
    (video標籤也是唷!因為這個節點不只被HTMLAudioElement繼承,還被HTMLVedioElement繼承。摁...你說繼承甚麼意思呢,沒關係我們後面的章節會提到)
  2. 節點MediaElementAudioSourceNode
    該節點可以將HTMLMediaElement,當作音訊來源Source。
  3. 節點GainNode
    接上原始音訊source後可以設定gainNode.gain.value的值來使調整該節點的音量
    (0~1等同於習慣上的0~100音量,大於1則會放大音量)
  4. 節點analyserNode
    該節點接上原始音訊source後,可以取得每個拿來做音頻分析。
    (頻率訊號經由傅立葉轉換過)
  5. 方法AudioNode.connect()
    connect方法扮演一個至關重要的角色,可以讓音訊經由多個節點(例如A-B-C-D),並分別做不同的音訊處理
  6. 音訊輸出BaseAudioContext.destination
    音訊處理完後,最後要記得連接(connect)到這個音訊的最終目的地(預設就是你的喇叭),如果沒有連接,就沒有聲音!

可是,這樣看不太懂

名詞有點多對吧!沒關係我們慢慢來,先從最簡單的開始:「如果我今天要在底層實作一個音樂播放器,可以怎麼做?」

  • 步驟 1 在HTML中設置audio標籤,並用js取得這一個HTMLMediaElement: audio
  • 步驟 2 透過createMediaElementSource方法來創建一個音訊來源(節點)
  • 步驟 3 將音訊來源連接到音訊輸出口
// 步驟 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 取得HTMLMediaElement
  • 步驟 2 分別用createMediaElementSource、createGain、createAnalyser方法創建三個節點
  • 步驟 3 把節點依序連接起來
  • 步驟 4 對節點的參數依序做設定
  • 步驟 5 利用今天的主角analyserNode取得音頻訊號並存到dataArray
// 步驟 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);

這樣是不是清楚多了呢?

AnalyserNode(專門為視覺化設計的節點)

這個步驟5是相當關鍵的一環,允許你取得音頻並進行分析,先來解釋出現的新東西:

  • analyserNode.fftSize
    影響傅立葉轉換(FFT)的精準程度,有電子學基礎的朋友應該就不陌生,這個值必須剛好為2的次方項,範圍則是介於2^5-2^15次方之間,預設為2048。
  • analyserNode.frequencyBinCount
    唯讀屬性,會是fftSize的一半,也是待會用getByteFrequencyData方法拿到陣列的長度

關於傅立葉(微積分),簡單來說,就是你做越多次,效果越好,花的時間越多;做越少次,效果越差(失真),花的時間越少。還是不太理解的朋友沒關係,你就把fftSize設個256也很夠用了!那麼你只需要準備長度為analyserNode.frequencyBinCount(剛好會是128)的陣列去裝訊號就好。若是上面的例子來說,長度會剛好是1024。

講到這聰明的你也應該明白了吧,如果不擔心取得的音訊失真的話,fftSize簡直是越小越好,陣列越短,對於效能的優化當然是越好!而dataArray代表了每個頻段的信號大小,fftSize越小,顯然頻寬越大,也因此才會失真。

所以怎麼看懂傅立葉轉換後的訊號?

這邊還有幾個知識點要補充:

  • dataArray每個索引的值都介於0~255之間
  • dataArray[index]中頻率跟index成正比,也就是頻率=index*(陣列長度/總頻率)=index*頻寬

而總頻率又被記錄在一開始的音源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文件,若要轉載請註明來源,感謝


上一篇
Chapter1-DJ最愛的音頻動感圖像(II)只要是認識Canvas的都覺得它很High歐
下一篇
Chapter1-DJ最愛的音頻動感圖像(IV)讓音樂動起來!開篇基礎設定和動畫框架
系列文
從零開始打造網頁遊戲-造輪子你也辦的到!31

尚未有邦友留言

立即登入留言