1. 程式人生 > 實用技巧 >【譯文】web audio api 入門

【譯文】web audio api 入門

原文地址: Getting Started with Web Audio API

在html5 <audio> 之前,打破web上的寧靜需要引入flash或者其他外掛。即使在web上不需要別的外掛,audio標籤在實現複雜遊戲和互動應用時也帶來了明顯的限制。

web audio api是一個在web上處理和合成音訊的高階js api。該api的目標包括現在遊戲音訊引擎的功能,以及在現代桌面音訊製作應用中的一些混合,處理和過濾任務。接下來是對此強大api的簡要介紹。

AudioContext入門

AudioContext是用於管理和播放所有聲音。使用web audio api來產生聲音,建立一個或多個聲音源,並將它們連線到由AudioContext 示例提供的聲音目的地。該連線不必直接連線,可以通過任意數量的中間AudioNodes 來作為音訊訊號的處理模組。這篇

文章對web audio進行了詳細的描述。

一個AudioContext 例項可以支援多個聲音輸入和複雜的音訊圖,所以在我們建立的每一個音訊應用中只需要一個例項。web audio api有許多有趣的功能,比如建立AudioNodes和解碼音訊檔案資料都是AudioContext 提供的方法。

建立AudioContext的程式碼片段:

var context;
window.addEventListener('load', init, false);
function init() {
  try {
    // Fix up for prefixing
    window.AudioContext = window.AudioContext||window.webkitAudioContext;
    context = new AudioContext();
  }
  catch(e) {
    alert('Web Audio API is not supported in this browser');
  }
}

對於基於WebKit-和Blink-的瀏覽器,目前需要webkit字首:webkitAudioContext。

載入聲音

web audio api用 AudioBuffer 來播放很短或者適中的聲音。獲取聲音檔案最基本的方法是使用XMLHttpRequest 。api支援載入多種格式的音訊檔案,比如WAV, MP3, AAC, OGG 和一些其他格式。瀏覽器支援的格式種類

載入聲音的程式碼示例片段:

var dogBarkingBuffer = null;
// Fix up prefixing
window.AudioContext = window.AudioContext || window.webkitAudioContext;
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 資訊。

一旦收到為解碼的音訊檔案資料,可以儲存起來隨後在解碼,或者立即可以用AudioContext 的decodeAudioData()方法來正確的解碼。這個方法會獲取儲存在request.response 中的音訊檔案資料ArrayBuffer,並進行非同步的解碼(不會阻塞主要的js執行執行緒)。

當decodeAudioData()完成後,它講呼叫一個回撥函式(提供解碼後的PCM音訊資料作為AudioBuffer).

播放聲音

一旦一個或者多個ArrayBuffers被載入後,就可開始播放聲音了。假設我們剛剛載入了帶有狗叫聲的AudioBuffer。然後我們就可以用下邊的程式碼播放這個buffer。

// Fix up prefixing
window.AudioContext = window.AudioContext || window.webkitAudioContext;
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.start(0);                           // play the source now
                                             // note: on older systems, may have to use deprecated noteOn(time);
}

每次當有人按下鍵盤或者用滑鼠進行點選操作, playSound()方法都會被呼叫。

start(time)方法可以使您輕鬆的為預習和其他時間嚴格的應用安排精確的聲音播放。當然,為了能使得這個安排工作正確執行,請確保已經預載入您的聲音緩衝(在一些老的系統上,你可能需要呼叫noteOn(time),而不是start(time))。

在ios上一個需要注意的點:蘋果會消音所有的音訊輸出知道使用者有互動事件發生。比如,在touch事件處理中呼叫playSound()。除非您瞭解這個問題,否則可能會在ios裝置上無法正常工作,為了避免此類問題,播放聲音最好在一個ui事件處理中(對使用者提醒,比如:點選此處播放)。

抽象化web audio api

當然,最好建立一個通用的載入系統,不用硬編碼來載入特定的聲音。

處理音訊應用或者遊戲的時候有許多處理中等長度或者適中長度的方法-這裡我們是用BufferLoader class

下邊的例子是如何使用BufferLoader 。建立2個AudioBuffers,載入後,同時播放它們。

window.onload = init;
var context;
var bufferLoader;

function init() {
  // Fix up prefixing
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
  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.start(0);
  source2.start(0);
}

處理time:以節奏播放聲音

web audio api允許開發者精確的播放。為了> 原文地址: Getting Started with Web Audio API

在html5 <audio> 之前,打破web上的寧靜需要引入flash或者其他外掛。即使在web上不需要別的外掛,audio標籤在實現複雜遊戲和互動應用時也帶來了明顯的限制。

web audio api是一個在web上處理和合成音訊的高階js api。該api的目標包括現在遊戲音訊引擎的功能,以及在現代桌面音訊製作應用中的一些混合,處理和過濾任務。接下來是對此強大api的簡要介紹。

AudioContext入門

AudioContext是用於管理和播放所有聲音。使用web audio api來產生聲音,建立一個或多個聲音源,並將它們連線到由AudioContext 示例提供的聲音目的地。該連線不必直接連線,可以通過任意數量的中間AudioNodes 來作為音訊訊號的處理模組。這篇文章對web audio進行了詳細的描述。

一個AudioContext 例項可以支援多個聲音輸入和複雜的音訊圖,所以在我們建立的每一個音訊應用中只需要一個例項。web audio api有許多有趣的功能,比如建立AudioNodes和解碼音訊檔案資料都是AudioContext 提供的方法。

建立AudioContext的程式碼片段:

var context;
window.addEventListener('load', init, false);
function init() {
  try {
    // Fix up for prefixing
    window.AudioContext = window.AudioContext||window.webkitAudioContext;
    context = new AudioContext();
  }
  catch(e) {
    alert('Web Audio API is not supported in this browser');
  }
}

對於基於WebKit-和Blink-的瀏覽器,目前需要webkit字首:webkitAudioContext。

載入聲音

web audio api用 AudioBuffer 來播放很短或者適中的聲音。獲取聲音檔案最基本的方法是使用XMLHttpRequest 。api支援載入多種格式的音訊檔案,比如WAV, MP3, AAC, OGG 和一些其他格式。瀏覽器支援的格式種類

載入聲音的程式碼示例片段:

var dogBarkingBuffer = null;
// Fix up prefixing
window.AudioContext = window.AudioContext || window.webkitAudioContext;
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 資訊。

一旦收到為解碼的音訊檔案資料,可以儲存起來隨後在解碼,或者立即可以用AudioContext 的decodeAudioData()方法來正確的解碼。這個方法會獲取儲存在request.response 中的音訊檔案資料ArrayBuffer,並進行非同步的解碼(不會阻塞主要的js執行執行緒)。

當decodeAudioData()完成後,它講呼叫一個回撥函式(提供解碼後的PCM音訊資料作為AudioBuffer).

播放聲音

一旦一個或者多個ArrayBuffers被載入後,就可開始播放聲音了。假設我們剛剛載入了帶有狗叫聲的AudioBuffer。然後我們就可以用下邊的程式碼播放這個buffer。

// Fix up prefixing
window.AudioContext = window.AudioContext || window.webkitAudioContext;
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.start(0);                           // play the source now
                                             // note: on older systems, may have to use deprecated noteOn(time);
}

每次當有人按下鍵盤或者用滑鼠進行點選操作, playSound()方法都會被呼叫。

start(time)方法可以使您輕鬆的為預習和其他時間嚴格的應用安排精確的聲音播放。當然,為了能使得這個安排工作正確執行,請確保已經預載入您的聲音緩衝(在一些老的系統上,你可能需要呼叫noteOn(time),而不是start(time))。

在ios上一個需要注意的點:蘋果會消音所有的音訊輸出知道使用者有互動事件發生。比如,在touch事件處理中呼叫playSound()。除非您瞭解這個問題,否則可能會在ios裝置上無法正常工作,為了避免此類問題,播放聲音最好在一個ui事件處理中(對使用者提醒,比如:點選此處播放)。

抽象化web audio api

當然,最好建立一個通用的載入系統,不用硬編碼來載入特定的聲音。

處理音訊應用或者遊戲的時候有許多處理中等長度或者適中長度的方法-這裡我們是用BufferLoader class

下邊的例子是如何使用BufferLoader 。建立2個AudioBuffers,載入後,同時播放它們。

window.onload = init;
var context;
var bufferLoader;

function init() {
  // Fix up prefixing
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
  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.start(0);
  source2.start(0);
}

處理time:以節奏播放聲音

web audio api允許開發者精確的播放。為了演示,我們建立一個簡單的節奏track。一個廣為人知的鼓點模式如下(看了下面的譜例和程式碼你可能搞不懂,但是你肯定聽過 動次打次 動地打次):

上圖中的 : 表示反覆記號,演奏的時候會重複一次(一共演奏兩小節)

為了方便大家理解這個節奏,我在網上找到了架子鼓的組成圖:

其中,每個八分音符會演奏hihat(譯者注:發出 ci 的聲音,架子鼓中的踩鑔hi-hat),這是個4/4拍(譯者注:一個小節有4個拍子),每一拍會交替kick(譯者注:發出 dong 的聲音,架子鼓中低音大鼓Bass Drum,又稱底鼓)和snare(譯者注:發出 dang 的聲音,架子鼓中的軍鼓snare drum)。假設我們已經載入了kick, snare hihat,執行此操作的程式碼很簡單:

// bar在音樂中值小節,這裡有兩小節
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 函式在指定的時間播放buffer:

function playSound(buffer, time) {
  var source = context.createBufferSource();
  source.buffer = buffer;
  source.connect(context.destination);
  source.start(time);
}

完整程式碼

改變聲音的音量

許多基礎操作之一就是改變音量。使用Web Audio API,我們通過GainNode將我們的聲音源路由到目的地來操縱音量。

這個連線設定可以通過以下程式碼試下:

// Create a gain node.
var gainNode = context.createGain();
// 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類應用的常見場景,我們有2個轉盤,我們希望能從一個聲源平移到另一個聲源。可以使用如下音訊圖來完成:

為了完成設定,我們只需要建立2個GainNode,使用以下的函式連線每一個源:

function createSource(buffer) {
  var source = context.createBufferSource();
  // Create a gain node.
  var gainNode = context.createGain();
  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
  };
}

等功率交叉淡入淡出

當您在樣本之間平移時,幼稚的線性交叉淡入淡出方法會顯示出音量下降。

為了解決這個問題,我們使用相等的功率曲線。其中相應的增益曲線是非線性的,並以較高的振幅相交。這樣可以最大程度的減少音訊區域之間的音量下降,從而使水平之間可能略有不同的區域之間的淡入淡出。

下面的程式碼使用 在2種音源之間控制淡入淡出: 完整程式碼

播放列表淡入淡出

另一個常見的漸變引用是音樂播放器。當切歌時,我們想淡出當前歌曲,然後淡入新的,避免造成過度不連貫。為此,請在將來安排交叉漸變。儘管我們使用setTimeout來進行排程,但是這並不精確。使用Web Audio API,我們使用AudioParam介面作為引數來規劃將來的值,例如GainNode的的增量值。

因此,給定一個播放列表,我們可以在當前播放曲目規劃一個增加量減少來切換曲目,並在下一首曲目增加增量,兩者都是在當前曲目播放之前。

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.start(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允許你將聲音從一個音訊節點傳輸到另一個,建立可能複雜的處理器鏈,為您的聲音形式新增複雜的效果。一種方法就是在聲音源和目標之間放置BiquadFilterNodes。這種型別的音訊節點可以執行各種低階過濾器,可用於構建圖形均衡器甚至更復雜的效果,主要與選擇聲音的頻譜的那一部分去強調。

過濾器支援的型別:

  • Low pass filter
  • High pass filter
  • Band pass filter
  • Low shelf filter
  • High shelf filter
  • Peaking filter
  • Notch filter
  • All pass filter

所有過濾器包含用於指定一定量的增益的引數,頻率過濾和品質過濾。Low-pass過濾保持很低的頻率範圍,放棄高頻。端點值由頻率值決定,Q因數是沒單位的,並決定了圖形的形狀。增益只會影響確定的過濾器,
比如low-shelf和peaking,而不會影響low-pass。

我們設定一個簡單的low-pass過濾,從聲音樣本中提取基數。

// 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 = 'lowpass'; // Low-pass filter. See BiquadFilterNode docs
filter.frequency.value = 440; // Set cutoff to 440 HZ
// Playback the sound.
source.start(0);

完整程式碼

通常情況下,頻率需要控制到對數刻度,因為人類的聽覺本身按照相同的原理工作(that is, A4 is 440hz, and A5 is 880hz)。

最後,請注意,示例程式碼可讓你斷開和連線過濾器,動態改變AudioContext圖。我們呼叫ode.disconnect(outputNumber)從圖上斷開AudioNodes。例如,要將圖形過濾重新路由到直接連線,我可以這樣:

// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);

進一步聆聽

我們已經介紹了基本的api知識,包括載入和播放音訊樣本。我們建立了帶有增益節點和過濾的音訊圖,以及計劃的聲音和音訊引數調整以啟動一些常見的聲音效果,此時,你就可以開始構建一些不錯的web音訊應用了。

如果你正在尋找靈感,許多開發者已經使用web autio api建立了出色的作品:

  • AudioJedit,一個在瀏覽器中使用SoundCloud 進行永久連結的聲音連結工具
  • ToneCraft,通過堆疊3d塊建立聲音,音序器
  • Plink,一個使用web audio和web socket的合作音樂製作遊戲