在 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 精確排定播放時間。為了示範這項功能,我們來設定簡單的節奏音軌。最廣為人知的鼓組模式可能是以下這個:
其中腳踏鈸每八分音符演奏一次,大鼓和小鼓則每四分音符交替演奏一次,節拍為 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 為 440hz,A5 為 880hz)。詳情請參閱上方原始碼連結中的 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 和 Web Sockets 的協作音樂製作遊戲。