利用HTML5 Web Audio API給網頁JS交互增加聲音


一、龐然的HTML5 Web Audio API

首先務必要弄清這一點,本文這里所說的HTML5 Web Audio API和HTML5 <audio>元素完全不是一個東西,其體量也完全不是一個等級的,HTML5 Web Audio API接口的豐富程度和體量可以和HTML canvas API相提並論,其能實現的功能也非常令人瞠目。

HTML5 Web Audio API可以讓我們無中生有創造聲音,而且是各種音調的聲音,換句話說,我們通過JavaScript就會創建一個完整的音樂出來,類似卡農將有固定的音樂,或是通過一些隨機算法創建隨機的音樂都都可以由前端開發工程師來完成(例如這個使用Web Audio API創建多個經典游戲背景音樂的案例),如果再配合一點人工智能的東西,我們說不定可以造出一個人工智能音樂生成工具。

當然其功能絕不僅限於這一點,我們還可以:

  • 對簡單或復雜的聲音進行混合;
  • 精確控制聲音的密度和節奏;
  • 內置淡入/淡出,顆粒噪點,音調控制等效果;
  • 靈活的處理在音頻流的聲道,使它們成為拆分和合並;
  • 處理從<audio>音頻或<video>視頻的媒體元素的音頻源;
  • 使用MediaStream的getUserMedia()方法事實處理現場輸入的音頻,例如變聲;
  • 立體音效,可以支持多種3D游戲和沉浸式環境;
  • 利用卷積引擎,創建各類線性音效,例如小/大房間、大教堂、音樂廳、洞穴、隧道、走廊、森林、圓形劇場等。尤其適用於創建高質量的房間效果。
  • 高效的實時的時域和頻分析,配合CSS3或Canvas或webGL可以實現音樂可視化支持;
  • 以及其他多種音頻波形算法控制,幾乎就是把一個音頻編輯類軟件搬到web上。

但是對大多數的Web開發人員,我們並不需要和音頻打交道這么深,因為畢竟不是做音樂網站的,此時,那些很高級的HTML5 Web Audio API功能就離我們實際工作的價值有些遠,如果貿然花大量時間去深入學習其實是一件投入產出比很低的事情。當然,我這種擔心實際上是多余的,“貿然花大量時間去深入學習?”搞反了吧,其實真實的現狀是開發人員對此鳥都不鳥,看都不看。一來因為畢竟不火嘛,二來資料少看不懂嘛,三來自己項目很少有聲音打交道,學了也白學。

考慮到這種現狀,如果本文內容洋洋灑灑講各種api,估計很多人瞟兩眼就直觀把頁面關掉了,所以接下來我們從一個最簡單最小的而且非常實用的案例來慢慢解開HTML5 Web Audio API的面紗。

二、HTML5 Web Audio API給網頁交互增加聲音

先來看實例效果,您可以狠狠地點擊這里:鼠標hover按鈕無中生有播放聲音demo

當我們鼠標hover下圖所示的按鈕的時候,在非IE瀏覽器下,就會播放出類似電子琴琴鍵按下的聲音,而且每次hover的時候,音調都會發生有規律的變化:

 

下面這句話是重點,demo頁面中播放的聲音是硬件自動生成的,不是調用現成的mp3音頻播放出來的。

換種說法就是:無需任何音頻文件,只需要幾行JS代碼,我們就能讓網頁發出各種各樣的音調。

10年前,flash炫酷網頁風興起的時候,交互音效是非常普遍的,可以說是flash酷站必備元素。只是后來flash衰落了,而web對於多媒體資源的支持長久以來是比較弱的,於是Web網站全部都是安安靜靜的。在移動端,H5廣告營銷性質的網頁都是音頻用的比較多,但大多都是作為背景音樂使用的,交互時候其實用的並不多。我想,可能與大多數開發並不知道HTML5 Web Audio API可以直接弄出聲音,知識的廣度局限了技術的選型。

於是我就想到,HTML5 Web Audio API提及的人不多,使用並不火,並不一定是因為這個技術本身的局限或者使用場景不多,而是具有較大影響力的人,社區或企業並沒有在這一塊身先士卒進行推動。很多企業的官網、產品宣傳頁,廣告營銷性質的專題活動非常講求炫酷,音視頻層出不窮,很顯然(參考flash時代),交互音效也其實是非常歡迎的,只是技術眼界的局限認為需要專門的mp3/wav音頻調用成本高而不使用罷了。

HTML5 Web Audio API可以直接產生聲音,並且可以隨意控制音調,時長以及淡入淡出等效果,更強大更靈活。而這個功能點是HTML5 Web Audio API中最簡單最基本也是最實用的,沒有理由不給web開發帶來交互音效熱潮。

三、網頁交互音效AudioContext,AudioParam等API介紹

下面就是聲音出現的JS相關代碼:

1.  window.AudioContext = window.AudioContext || window.webkitAudioContext;
2.  var audioCtx = new AudioContext();
3.  var oscillator = audioCtx.createOscillator();
4.  var gainNode = audioCtx.createGain();
5.  oscillator.connect(gainNode);
6.  gainNode.connect(audioCtx.destination);
7.  oscillator.type = 'sine';
8.  oscillator.frequency.value = 196.00;
9.  gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
10. gainNode.gain.linearRampToValueAtTime(1, audioCtx.currentTime + 0.01);
11. oscillator.start(audioCtx.currentTime);
12. gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1);
13. oscillator.stop(audioCtx.currentTime + 1);

總共13行代碼,雖然行數不多,但其中出現的API數量以及其中的含義還是有點厚度的。

我們一行一行來看。

1. window.AudioContext = window.AudioContext || window.webkitAudioContext
沒啥好說的,向前兼容一下老的webkit瀏覽器。

2. var audioCtx = new AudioContext()
AudioContext是HTML5 Web Audio最基礎估計也是最常用的API了,其角色和地位可以和Canvas的canvas.getContext一拼。顧名思義,指定一個音頻上下文,包含大量的屬性和方法,而且這些方法名又多又長,根本就記不住,隨便截個圖感受一下

AudioContext方法名截圖

所以,必須要有文檔:MDN-AudioContext

但,不要怕,對於簡單的音調播放,我們只需要關心下面幾個屬性和方法就好了,上面那么多高級的東西都是留給需要深入和音頻打交道的開發人員用的。

3. var oscillator = audioCtx.createOscillator();
createOscillator()AudioContext的一個方法,創建一個OscillatorNode。 OscillatorNode表示一個周期性波形,比如一個正弦波(就是上下彎彎的那個波),大家應該都知道聲音本質其實就是震動,是和波形緊密關聯的,因此波形不一樣,最終的聲音也就不一樣,例如有的波形是“嗶嗶嗶…”,有的波形出來的是“嘟嗚嗚…”。波還受頻率影響,最終表現為不一樣的音調高低。因此,OscillatorNode就有frequencytype下面2個即將登場的屬性。

總之,這里的變量oscillator基本上來說創造了一個音調。

4. var gainNode = audioCtx.createGain()
createGain()也是AudioContext的一個方法,創建一個GainNode,它可以控制音頻的總音量。也就是說createOscillator()控制音調,createGain()控制音量,互相配合,黃金搭檔。

GainNode只有一個簡單的只讀屬性,名為gain,完整書寫GainNode.gain,本身沒什么,但是GainNode.gain的返回值可是個大家伙,是個a-rate類型的AudioParam

AudioParam總共有2個類型,一個是a-rate還有一個是k-rate,前者AudioParam為音頻信號每個樣品幀的當前聲音參數值;后者的AudioParam使用整個塊(即128個樣本幀)相同的初始音頻參數值。

AudioParam包含諸多屬性和方法,都是與音量控制相關的。

5. oscillator.connect(gainNode)
音調和音量關聯。

6. gainNode.connect(audioCtx.destination)
這里的audioCtx.destination返回AudioDestinationNode對象,AudioDestinationNode接口表示音頻圖形在特定情況下的最終輸出地址 – 通常為揚聲器。話句話說就是和音頻設備關聯。

這個關聯為audio context中所有節點的最終節點,看下面的工作流圖:

audio context工作流

7. oscillator.type = ‘sine’
我們的聲音波形指定為'sine',也就是正弦波,還支持其他3種波形,為'square'方波,'triangle'三角波以及'sawtooth'鋸齒波。形狀如下圖(圖來自css-tricks

4種默認的波形圖

下面這個Codepen可以感受4中波形帶來的嗶嗶啵啵的聲音:

以上四種波形是內置的,我們還可以使用setPeriodicWave()方法自定義波形。

8. oscillator.frequency.value = 196.00
設置波形的頻率,實際上,可以理解為設置聲音的音調,也就是設置最后的聲音是“多瑞米發嗦啦西”其中的一個。數值越小,聲音越低沉,越大提琴;數值越大,聲音越清脆,越小提琴。

demo頁面中的音調完整范圍如下:

[196.00, 220.00, 246.94, 261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25, 587.33, 659.25, 698.46, 783.99, 880.00, 987.77, 1046.50]

每次鼠標hover我都會變換一個頻率,這就是為什么每次經過按鈕就會聽到有規律的音調變化。

9. gainNode.gain.setValueAtTime(0, audioCtx.currentTime)
先來了解下audioCtx.currentTime,其值是以雙精度浮點型數字返回硬件調用的秒數。只讀,也就是說這個時間你是無法改變的,無論是暫停或者重置都不可以。當我們使用類似new AudioContext()創建AudioContext的時候,這個時間就可以從0開始走了,然后就像離弦的箭,一直不回頭。

gainNode.gain這個前面介紹過了,返回的是AudioParam,所以這里實際上執行的是AudioParam.setValueAtTime()方法,此方法支持兩個參數,一個是音量(范圍0~1),一個時間,這里setValueAtTime(0, audioCtx.currentTime)表示當下就把音量設為0,也就是沒聲音,因為快速鼠標hover時候,之前聲音可能還沒播放結束,需要從0開始。

10. gainNode.gain.linearRampToValueAtTime(1, audioCtx.currentTime + 0.01)
linearRampToValueAtTime()方法表示音量在某時間線性變化到某值,這里linearRampToValueAtTime(1, audioCtx.currentTime + 0.01)實際上表示的是在0.01秒的時間內,聲音音量線性從01

11. oscillator.start(audioCtx.currentTime)
開始出聲啦……

12. gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1)
exponentialRampToValueAtTime()方法表示音量在某時間指數變化到某值,這里的exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1)實際上表示的是在1.00秒的時間內,音量由之前的1以指數曲線的速度降到極低的0.001音量。

我們把10, 12行代碼聯系起來,可能就是這樣一個曲線(自己手繪的,湊合看吧):
聲音曲線圖

這樣處理,聲音消失就有“念念不忘,必有回響”的效果,就很有琴鍵按下去聲音的感覺。

13. oscillator.stop(audioCtx.currentTime + 1)
1秒后,聲音關閉。

最終,我們把源碼和注釋重新整合下就有:

// 創建音頻上下文  
var audioCtx = new AudioContext();
// 創建音調控制對象  
var oscillator = audioCtx.createOscillator();
// 創建音量控制對象  
var gainNode = audioCtx.createGain();
// 音調音量關聯  
oscillator.connect(gainNode);
// 音量和設備關聯  
gainNode.connect(audioCtx.destination);
// 音調類型指定為正弦波  
oscillator.type = 'sine';
// 設置音調頻率  
oscillator.frequency.value = 196.00;
// 先把當前音量設為0  
gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
// 0.01秒時間內音量從剛剛的0變成1,線性變化 
gainNode.gain.linearRampToValueAtTime(1, audioCtx.currentTime + 0.01);
// 聲音走起 
oscillator.start(audioCtx.currentTime);
// 1秒時間內音量從剛剛的1變成0.001,指數變化 
gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1);
// 1秒后停止聲音 
oscillator.stop(audioCtx.currentTime + 1);

這下就好理解多了!

四、結束語

本文展示的交互音效僅僅是HTML5 Web Audio API眾多特性中的冰山一角,但已經足夠讓廣告性質類的網頁熠熠生輝,甚至傳統web站點在一些特殊場合也是可以嘗試使用的。

最后,介紹幾個和web audio相關的JS庫,Pizzicato.jswebaudiox.jshowler.jsWADTone.js,適合復雜音頻處理場景。

HTML5 Web Audio API Chrome,Safari以及Firefox瀏覽器都是支持的,兼容性還是不錯的。

參考文章:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM