題圖:Egor Khomiakovcss
注:本文同時發佈在 知乎專欄html
首先引用一下 MDN 上對 Web Audio Api 的一段描述:html5
The Web Audio API involves handling audio operations inside an audio context, and has been designed to allow modular routing. Basic audio operations are performed with audio nodes, which are linked together to form an audio routing graph.node
大體的意思就是 Web Audio API 須要在音頻上下文中處理音頻的操做,並具備模塊化路由的特色。基本的音頻操做是經過音頻節點來執行的,這些音頻節點被鏈接在一塊兒造成音頻路由圖。webpack
咱們能夠從上面這段文字中提取出幾個關鍵詞:git
我將會以這些關鍵詞爲開始,慢慢介紹什麼是 Web Audio Api,如何使用 Web Audio Api 來處理音頻等等。github
AudioContext
)音頻中的 AudioContext
能夠類比於 canvas
中的 context
,其中包含了一系列用來處理音頻的 API
,簡而言之,就是能夠用來控制音頻的各類行爲,好比播放、暫停、音量大小等等等等。建立音頻的 context
比建立 canvas
的 context
簡單多了(考慮代碼的簡潔性,下面代碼都不考慮瀏覽器的兼容狀況):web
const audioContext = new AudioContext();複製代碼
在繼續瞭解 AudioContext
以前,咱們先來回顧一下,平時咱們是如何播放音頻的:ajax
<audio autoplay src="path/to/music.mp3"></audio>複製代碼
或者:canvas
const audio = new Audio();
audio.autoplay = true;
audio.src = 'path/to/music.mp3';複製代碼
沒錯,很是簡單的幾行代碼就實現了音頻的播放,可是這種方式播放的音頻,只能控制播放、暫停等等一些簡單的操做。可是若是咱們想要控制音頻更「高級」的屬性呢,好比聲道的合併與分割、混響、音調、聲相控制和音頻振幅壓縮等等,能夠作到嗎?答案固然是確定的,一切都基於 AudioContext
。咱們以最簡單的栗子來了解一下 AudioContext
的用法:
const URL = 'path/to/music.mp3';
const audioContext = new AudioContext();
const playAudio = function (buffer) {
const source = audioContext.createBufferSource();
source.buffer = buffer;
source.connect(audioContext.destination);
source.start();
};
const getBuffer = function (url) {
const request = new XMLHttpRequest();
return new Promise((resolve, reject) => {
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = () => {
audioContext.decodeAudioData(request.response, buffer => buffer ? resolve(buffer) : reject('decoding error'));
};
request.onerror = error => reject(error);
request.send();
});
};
const buffer = await getBuffer(URL);
buffer && playAudio(buffer);複製代碼
別方,這個栗子真的是最簡單的栗子了(儘可能寫得簡短易懂了),其實仔細看下,代碼無非就作了三件事:
ajax
把音頻數據請求下來;audioContext.decodeAudioData()
方法把音頻數據轉換成咱們所須要的 buffer
格式;playAudio()
方法把音頻播放出來。你沒猜錯,達到效果和剛剛提到的播放音頻的方式一毛同樣。這裏須要重點講一下 playAudio
這個函數,我提取出了三個關鍵點:
source
connect
destination
你能夠試着以這種方式來理解這三個關鍵點:首先咱們經過 audioContext.createBufferSource()
方法建立了一個「容器」 source
並裝入接收進來的「水」 buffer
;其次經過「管道」 connect
把它和「出口」 destination
鏈接起來;最終「出口」 destination
「流」出來的就是咱們所聽到的音頻了。不知道這麼講,你們有沒有比較好理解。
或者也能夠拿 webpack 的配置文件來類比:
module.exports = {
// source.buffer
entry: 'main.js',
// destination
output: {
filename: 'app.js',
path: '/path/to/dist',
},
};複製代碼
source
和 destination
分別至關於配置中的入口文件和輸出文件,而 connect
至關於 webpack 內置的默認 loader
,負責把源代碼 buffer
生成到輸出文件中。
重點理解這三個關鍵點的關係。
注意:Audio
和 Web Audio 是不同的,它們之間的關係大概像這樣:
Audio
:
Web Audio:
AudioNode
)到這裏,你們應該大體知道了如何經過 AudioContext
去控制音頻的播放。可是會發現寫了這麼一大堆作的事情和前面提到的一行代碼的所作的事情沒什麼區別(<audio autoplay src="path/to/music.mp3"></audio>
),那麼 AudioContext
具體是如何去處理咱們前面所提到的那些「高級」的功能呢?就是咱們接下來正要了解的 音頻節點。
那麼什麼是音頻節點呢?能夠把它理解爲是經過「管道」 connect
鏈接在「容器」source
和「出口」 destination
之間一系列的音頻「處理器」。AudioContext
提供了許多「處理器」用來處理音頻,好比音量「處理器」 GainNode
、延時「處理器」 DelayNode
或聲道合併「處理器」 ChannelMergerNode
等等。
前面所提到的「管道」 connect
也是由音頻節點 AudioNode
提供的,因此你猜的沒錯,「容器」 source
也是一種音頻節點。
const source = audioContext.createBufferSource();
console.log(source instanceof AudioNode); // true複製代碼
AudioNode
還提供了一系列的方法和屬性:
.context
(read only): audioContext
的引用.channelCount
: 聲道數.connect()
: 鏈接另一個音頻節點.start()
: 開始播放.stop()
: 中止播放更多詳細介紹可訪問 MDN 文檔。
前面有提到音頻處理是經過一個個「處理器」來處理的,那麼在實際應用中怎麼把咱們想要的「處理器」裝上去呢?
Don't BB, show me the code:
const source = audioContext.createBufferSource();
const gainNode = audioContext.createGain();
const buffer = await getBuffer(URL);
source.buffer = buffer;
source.connect(gainNode);
gainNode.connect(source.destination);
const updateVolume = volume => gainNode.gain.value = volume;複製代碼
能夠發現和上面提到的 playAudio
方法很像,區別只是 source
不直接 connect 到 source.destination
,而是先 connect 到 gainNode
,而後再經過 gainNode
connect 到 source.destination
。這樣其實就把「音量處理器」裝載上去了,此時咱們經過更新 gainNode.gain.value
的值(0 - 1
之間)就能夠控制音量的大小了。
不知道怎麼翻譯這個「處理器」,暫且叫作低階濾波器吧,簡單來講它就是一個經過過濾音頻的數字信號進而達到控制 音調 的音頻節點。把它裝上:
const filterNode = audioContext.createBiquadFilter();
// ...
source.connect(filterNode);
filterNode.connect(source.destination);
const updateFrequency = frequency => filterNode.frequency.value = frequency;複製代碼
這樣一來咱們就能夠經過 updateFrequency()
方法來控制音頻的音調(頻率)了。固然,除了 frequency
咱們還能夠調整的屬性還有(MDN Docs):
.Q
: quality factor;.type
: lowpass, highpass, bandpass, lowshelf, highshelf, peaking, notch, allpass;.detune
: detuning of the frequency in cents.咱們能夠調用 PannerNode 的 .setPosition()
方法來作出很是有意思的 3D 環繞音效:
<input type="range" name="rangeX" value="0" max="10" min="-10">複製代碼
const rangeX = document.querySelector('input[name="rangeX"]');
const source = audioContext.createBufferSource();
const pannerNode = audioContext.createPanner();
source.connect(pannerNode);
pannerNode.connect(source.destination);
rangeX.addEventListener('input', () => pannerNode.setPosition(rangeX.value, 0, 0));複製代碼
仍是老方法「裝上」 PannerNode
「處理器」,而後經過監聽 range
控件的 input
事件,經過 .setPosition()
方法更新 聲源相對於聽音者的位置,這裏我只簡單的更新了聲源相對於聽音者的 X
方向上的距離,當值爲負值時,聲音在左邊,反之則在右邊。
你能夠這麼去理解 PannerNode
,它把你(聽音者)置身於一個四面八方都很是空曠安靜的空間中,其中還有一個音響(聲源),而 .setPosition()
方法就是用來控制 音響 在空間中 相對於你(聽音者) 的位置的,因此上面這段代碼能夠控制聲源在你左右倆耳邊來回晃動(帶上耳機)。
固然,對於 PannerNode
來講,還有許多屬性可使得 3D 環繞音效聽上去更逼真,好比:
.distanceModel
: 控制音量變化的方式,有 3 種可能的值:linear
, inverse
和 exponential
;.maxDistance
: 表示 聲源 和 聽音者 之間的最大距離,超出這個距離後,聽音者將再也不能聽到聲音;.rolloffFactor
: 表示當 聲源 遠離 聽音者 的時候,音量以多快的速率減少;這裏只列舉了經常使用的幾個,若是想進一步瞭解 PannerNode
能作什麼的話,能夠查閱 MDN 上的 文檔。
前面有提到過,在 AudioContext
中能夠同時使用多個「處理器」去處理一個音頻源,那麼多個音頻源 source
能夠同時輸出嗎?答案固然也是確定的,在 AudioContext
中能夠有多個音頻處理通道,它們之間互不影響:
const sourceOne = audioContext.createBufferSource();
const sourceTwo = audioContext.createBufferSource();
const gainNodeOne = audioContext.createGain();
const gainNodeTwo = audioContext.createGain();
sourceOne.connect(gainNodeOne);
sourceTwo.connect(gainNodeTwo);
gainNodeOne.connect(audioContext.destination);
gainNodeTwo.connect(audioContext.destination);複製代碼
Modular
)經過前面 音頻節點 的介紹,相信大家已經感覺到了 Web Audio 的模塊化設計了,它提供了一種很是方便的方式來爲音頻裝上(connect
)不一樣的「處理器」 AudioNode
。不只一個音頻源可使用多個「處理器」,而多個音頻源也能夠合併爲一個「輸出」 destination
。
得益於 Web Audio 的模塊化設計,除了上面提到的模塊(AudioNode
),它還提供了很是多的可配置的、高階的、開箱即用的模塊。因此經過使用這些模塊,咱們徹底能夠建立出功能豐富的音頻處理應用。
若是你對 AudioContext
和 AudioNode
之間的關係尚未一個比較清晰的概念的話,就和前面一開始所說的那樣,把它們和 webpack 和 loader
作類比,AudioContext
和 webpack 至關於一個「環境」,模塊(AudioNode
或 loader
)能夠很方便在「環境」中處理數據源(AudioContext
中的 buffer
或 webpack 中的 js
, css
, image
等靜態資源),對好比下:
module.exports = {
entry: {
// 多音頻源合併爲一個輸出
app: ['main.js'], // source.buffer
vender: ['vender'], // source.buffer
},
output: { // source.destination
filename: 'app.js',
path: '/path/to/dist',
},
// AudioNode
module: {
rules: [{
// source.buffer
test: /\.(scss|css)$/,
// AudioNode: GainNode, BiquadFilterNode, PannerNode ...
use: ['style-loader', 'css-loader', 'sass-loader'],
}],
},
};複製代碼
再次發現,Web Audio Api 和 webpack 的設計理念如此的類似。
Audio Graph
)An audio graph is a set of interconnected audio nodes.
如今咱們知道了,音頻的處理都是經過 音頻節點 來處理的,而多個音頻節點 connect
到一塊兒就造成了 音頻導向圖(Audio Routing Graph),簡而言之就是多個相互鏈接在一塊兒的音頻節點。
本文展現的僅僅只是 Web Audio 衆多 API 中的冰山一角,若是想更深刻了解 Web Audio 的話,建議能夠去查閱相關文檔。儘管如此,利用上面介紹的一些 API 也足夠作出一些有意思的音樂效果來了。