在 HTML5 <audio> 元素出現之前,網頁必須使用 Flash 或其他外掛程式才能發出聲音。雖然網頁上的音訊不再需要外掛程式,但音訊標記在實作精緻遊戲和互動式應用程式時,會帶來顯著限制。
Web Audio API 是高階 JavaScript API,可在網路應用程式中處理及合成音訊。這個 API 的目標是納入現代遊戲音訊引擎的功能,以及現代桌上型音訊製作應用程式中的部分混音、處理和篩選工作。以下將簡要介紹如何使用這項強大的 API。
開始使用 AudioContext
AudioContext 用於管理及播放所有聲音。如要使用 Web Audio API 產生聲音,請建立一或多個音效來源,並將其連線至 AudioContext 執行個體提供的音效目的地。這個連線不一定要是直接連線,可以透過任意數量的中間 AudioNodes,做為音訊訊號的處理模組。如要進一步瞭解這項路徑,請參閱 Web Audio 規格。
單一 AudioContext 執行個體可以支援多個聲音輸入和複雜的音訊圖表,因此我們為建立的每個音訊應用程式只需要一個這類執行個體。
下列程式碼片段會建立 AudioContext:
var context;
window.addEventListener('load', init, false);
function init() {
try {
context = new AudioContext();
}
catch(e) {
alert('Web Audio API is not supported in this browser');
}
}
如果是舊版 WebKit 架構的瀏覽器,請使用 webkit 前置字串,就像 webkitAudioContext 一樣。
許多有趣的 Web Audio API 功能 (例如建立 AudioNode 和解碼音訊檔案資料) 都是 AudioContext 的方法。
正在載入音效
Web Audio API 會使用 AudioBuffer 處理短到中等長度的聲音。基本方法是使用 XMLHttpRequest 擷取音效檔案。
這項 API 支援載入多種格式的音訊檔案資料,例如 WAV、MP3、AAC、OGG 和其他格式。不同音訊格式的瀏覽器支援情形不一。
以下程式碼片段示範如何載入音效樣本:
var dogBarkingBuffer = null;
var context = new AudioContext();
function loadDogSound(url) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
dogBarkingBuffer = buffer;
}, onError);
}
request.send();
}
音訊檔案資料為二進位 (非文字),因此我們將要求的 responseType
設為 'arraybuffer'。如要進一步瞭解 ArrayBuffers,請參閱這篇 XHR2 相關文章。
收到 (未解碼) 音訊檔案資料後,可以保留這些資料以供日後解碼,也可以使用 AudioContext decodeAudioData() 方法立即解碼。這個方法會採用 request.response 中儲存的音訊檔案資料 ArrayBuffer,並以非同步方式解碼 (不會封鎖主要 JavaScript 執行緒)。
decodeAudioData() 完成後,會呼叫回呼函式,並以 AudioBuffer 形式提供解碼的 PCM 音訊資料。
播放音效
載入一或多個 AudioBuffers 後,即可播放音效。假設我們剛載入 AudioBuffer,其中包含狗吠聲,且載入作業已完成。接著,我們可以使用下列程式碼播放這個緩衝區。
var context = new AudioContext();
function playSound(buffer) {
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.noteOn(0); // play the source now
}
每當有人按下按鍵或使用滑鼠點選項目時,都可以呼叫這個 playSound() 函式。
noteOn(time) 函式可讓遊戲和其他時間關鍵型應用程式輕鬆安排精確的音訊播放時間。不過,如要讓這項排程功能正常運作,請務必預先載入音訊緩衝區。
將 Web Audio API 抽象化
當然,最好是建立更通用的載入系統,而非以硬式編碼的方式載入這個特定音效。音訊應用程式或遊戲會使用許多短到中等長度的聲音,處理這些聲音的方法有很多,以下是其中一種方法,使用 BufferLoader (不屬於網路標準)。
以下是 BufferLoader 類別的使用範例。
讓我們建立兩個 AudioBuffers,並在載入後同時播放。
window.onload = init;
var context;
var bufferLoader;
function init() {
context = new AudioContext();
bufferLoader = new BufferLoader(
context,
[
'../sounds/hyper-reality/br-jam-loop.wav',
'../sounds/hyper-reality/laughter.wav',
],
finishedLoading
);
bufferLoader.load();
}
function finishedLoading(bufferList) {
// Create two sources and play them both together.
var source1 = context.createBufferSource();
var source2 = context.createBufferSource();
source1.buffer = bufferList[0];
source2.buffer = bufferList[1];
source1.connect(context.destination);
source2.connect(context.destination);
source1.noteOn(0);
source2.noteOn(0);
}
處理時間:播放有節奏的聲音
開發人員可透過 Web Audio API 精確安排播放時間。為示範這項功能,我們來設定簡單的節奏軌。最廣為人知的鼓組模式可能是以下模式:
每八分音符會播放一次 Hi-Hat,每四分音符會交替播放一次大鼓和軍鼓,拍號為 4/4。
假設我們已載入 kick、snare 和 hihat 緩衝區,執行這項操作的程式碼很簡單:
for (var bar = 0; bar < 2; bar++) {
var time = startTime + bar * 8 * eighthNoteTime;
// Play the bass (kick) drum on beats 1, 5
playSound(kick, time);
playSound(kick, time + 4 * eighthNoteTime);
// Play the snare drum on beats 3, 7
playSound(snare, time + 2 * eighthNoteTime);
playSound(snare, time + 6 * eighthNoteTime);
// Play the hi-hat every eighth note.
for (var i = 0; i < 8; ++i) {
playSound(hihat, time + i * eighthNoteTime);
}
}
這裡我們只重複一次,而不是樂譜中看到的無限迴圈。函式 playSound 是一種方法,可於指定時間播放緩衝區,如下所示:
function playSound(buffer, time) {
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.noteOn(time);
}
調整音效音量
您可能想對音訊執行的最基本作業之一,就是變更音量。使用 Web Audio API 時,我們可以透過 AudioGainNode 將來源路徑傳送到目的地,藉此操控音量:

如要設定這項連線,請按照下列步驟操作:
// Create a gain node.
var gainNode = context.createGainNode();
// Connect the source to the gain node.
source.connect(gainNode);
// Connect the gain node to the destination.
gainNode.connect(context.destination);
設定圖表後,您可以透過操控 gainNode.gain.value,以程式輔助方式變更音量,如下所示:
// Reduce the volume.
gainNode.gain.value = 0.5;
在兩種聲音之間淡入淡出
現在,假設我們有稍微複雜一點的情境,也就是播放多個音效,但想在音效之間進行交叉淡化。在 DJ 類型的應用程式中,這是常見的情況,因為我們有兩個轉盤,並希望能夠從一個音源平移到另一個音源。
您可以使用下列音訊圖表執行這項操作:
如要進行這項設定,只要建立兩個 AudioGainNodes,然後透過節點連線至每個來源,使用類似下列函式的項目:
function createSource(buffer) {
var source = context.createBufferSource();
// Create a gain node.
var gainNode = context.createGainNode();
source.buffer = buffer;
// Turn on looping.
source.loop = true;
// Connect source to gain.
source.connect(gainNode);
// Connect gain to destination.
gainNode.connect(context.destination);
return {
source: source,
gainNode: gainNode
};
}
等功率淡入淡出
如果您採用簡單的線性交叉淡化方法,在樣本之間平移時,音量會下降。
為解決這個問題,我們使用等功率曲線,其中對應的增益曲線為非線性,且在較高的振幅處相交。這樣可減少音訊區域之間的音量落差,讓音量略有差異的區域之間能更平順地淡入淡出。
播放清單淡入淡出
另一個常見的交叉推桿應用程式是音樂播放器應用程式。歌曲變更時,我們希望淡出目前的曲目,並淡入新曲目,以免轉場效果不協調。如要這樣做,請排定未來的淡入淡出時間。雖然我們可以使用 setTimeout 進行這項排程,但這並不精確。透過 Web Audio API,我們可以運用 AudioParam 介面,為參數 (例如 AudioGainNode 的增益值) 排定日後的值。
因此,如果提供播放清單,我們可以排定目前播放曲目的增益減少,以及下一首曲目的增益增加,在目前曲目播放完畢前稍微調整,藉此在曲目間轉換:
function playHelper(bufferNow, bufferLater) {
var playNow = createSource(bufferNow);
var source = playNow.source;
var gainNode = playNow.gainNode;
var duration = bufferNow.duration;
var currTime = context.currentTime;
// Fade the playNow track in.
gainNode.gain.linearRampToValueAtTime(0, currTime);
gainNode.gain.linearRampToValueAtTime(1, currTime + ctx.FADE_TIME);
// Play the playNow track.
source.noteOn(0);
// At the end of the track, fade it out.
gainNode.gain.linearRampToValueAtTime(1, currTime + duration-ctx.FADE_TIME);
gainNode.gain.linearRampToValueAtTime(0, currTime + duration);
// Schedule a recursive track change with the tracks swapped.
var recurse = arguments.callee;
ctx.timer = setTimeout(function() {
recurse(bufferLater, bufferNow);
}, (duration - ctx.FADE_TIME) - 1000);
}
Web Audio API 提供一組便利的 RampToValue 方法,可逐步變更參數值,例如 linearRampToValueAtTime 和 exponentialRampToValueAtTime。
您可以從內建的線性和指數函式中挑選轉場效果時間函式 (如上所示),也可以使用 setValueCurveAtTime 函式,透過值陣列指定自己的值曲線。
為聲音套用簡單的濾鏡效果

Web Audio API 可讓您將音訊從一個音訊節點傳輸到另一個節點,建立可能複雜的處理器鏈結,為音訊波形新增複雜效果。
其中一種做法是在音效來源和目的地之間放置 BiquadFilterNode。這類音訊節點可執行各種低階濾波器,用於建構圖形等化器,甚至是更複雜的音效,主要用於選取要強調的聲音頻譜部分,以及要抑制的部分。
支援的篩選器類型包括:
- 低通濾波器
- 高通濾波器
- 帶通濾波器
- 低音架濾鏡
- 高音架濾鏡
- 尖峰濾波器
- 凹口濾波器
- 全部通過篩選器
所有篩選器都包含參數,可指定某個增益量、套用篩選器的頻率,以及品質因數。低通濾波器會保留較低的頻率範圍,但捨棄高頻。截斷點取決於頻率值,Q 因子則沒有單位,並決定圖表的形狀。增益只會影響特定濾鏡,例如低架和峰值濾鏡,不會影響這個低通濾鏡。
讓我們設定簡單的低通濾波器,只從音訊樣本中擷取低音:
// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
source.connect(filter);
filter.connect(context.destination);
// Create and specify parameters for the low-pass filter.
filter.type = 0; // Low-pass filter. See BiquadFilterNode docs
filter.frequency.value = 440; // Set cutoff to 440 HZ
// Playback the sound.
source.noteOn(0);
一般來說,頻率控制項需要經過調整,才能在對數刻度上運作,因為人類聽覺本身就是依據相同原理運作 (也就是說,A4 為 440 Hz,A5 為 880 Hz)。詳情請參閱上方原始碼連結中的 FilterSample.changeFrequency 函式。
最後請注意,範例程式碼可讓您連線及中斷篩選器,動態變更 AudioContext 圖表。我們可以呼叫 node.disconnect(outputNumber),從圖表中中斷 AudioNode 連線。舉例來說,如要將圖表從經過篩選器重新導向至直接連線,可以採取下列做法:
// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);
進一步聆聽
我們已介紹過 API 的基本概念,包括載入及播放音訊樣本。我們已建構音訊圖表,其中包含增益節點和濾波器,並排定聲音和音訊參數調整時間,以啟用一些常見音效。現在,您已準備好建構一些絕佳的網頁音訊應用程式!
如需靈感,許多開發人員已使用 Web Audio API 創作精彩作品。我最喜歡的包括:
- AudioJedit:瀏覽器內建的聲音剪接工具,使用 SoundCloud 永久連結。
- ToneCraft:音序器,可透過堆疊 3D 方塊建立音效。
- Plink:使用 Web Audio 和 WebSocket 協作製作音樂的遊戲。