初探 Web Audio API
隨著 web 技術的蓬勃發展,在網頁裡播放音樂已經可以不需要透過 flash 或 quicktime 等外掛程式輔助,純粹使用 HTML5 的 audio tag 就可以播放音樂,甚至可以透過 W3C 所規範的 Web Audio API 就可以做許多進階的聲音控制和應用,接觸到的時候覺得實在太有趣,一定要寫一些文章在記錄一下。
Web Audio API 參考:http://webaudio.github.io/web-audio-api/
Web Audio API 起手式
要使用 Web Audio API,第一步就是要創建一個 AudioContext,AudioContext 是一個聲音的容器,在 AudioContext 裡,我們可以針對聲音做各種的處理,在裡頭,AudioSourceNode 表示音訊來源,而聲音是以節點的方式 ( AudioNode ) 存在,每個節點都有輸入和輸出,只有來源節點沒有輸入,聲音除了可以用外部的輸入 ( wav、mp3、ogg...等 ),也可以使用振盪器 ( Oscillators ) 來產生,原理就有點像我們彈奏弦樂器,也是利用樂器內的弦震盪來發聲。
既然是初探 Web Audio API,就用一個簡單的例子入手,由於我們在範例裡要用的是 Web Audio API,就一定會用到 AudioContext,所以我們要先來看看瀏覽器有沒有支援 ( 行動裝置目前還不支援 ),一開始先這樣寫,套用有支援的 AudioContext。
var AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new window.AudioContext;
振盪器的屬性
有了裝載聲音的容器之後,接著就用振盪器來發點聲音,先用createOscillator()
來產生振盪器,振盪器有四種屬性:
type:
type
選擇震盪的波形,波形共有五種 type,分別是 sine ( 正弦波 )、square ( 方波 )、sawtooth ( 鋸齒波 )、triangle ( 三角波 ) 和 custom ( 自訂 ),每種波形在同樣的頻率下,會產生不同的聲音。frequency:
震盪的頻率,在音樂裡面也稱作「音高」( 可以參考維基百科 ),頻率越高聲音就越高,基本預設的聲音頻率座落在 440 赫茲,因為任何樂器演奏中央 C 上的 A 音符基頻皆為 440 赫茲。
detune:
音高偏移微調,將原音的音分做些微移調,造成重疊的音色效果,也就是雖然是同一個音,但經過 detune 偏移,就變成了不同的聲音。( 音分可以參考維基百科 )
onended:
結束時會發生的事件,寫法為:
oscillator.onended = function(){發生什麼事};
。
利用這四個屬性,我們就可以這樣做,建立三個音節,讓這三個音節做 detune 偏移,並且只播放兩秒後停止 ( 直接寫在 stop 裡面,要延後播放就寫在 start 裡 ),停止後會彈出一個 alert 告訴我們音樂停了。( 範例:web-audio-api-demo01.html )
var oscillator1 = context.createOscillator();
oscillator1.type = "square";
oscillator1.frequency.value = 440;
oscillator1.detune.value = -400;
oscillator1.connect(context.destination);
oscillator1.start();
oscillator1.stop(2);
oscillator1.onended = function(){alert('stop');};
var oscillator2 = context.createOscillator();
oscillator2.type = "square";
oscillator2.frequency.value = 440;
oscillator2.detune.value = -300;
oscillator2.connect(context.destination);
oscillator2.start();
oscillator2.stop(2);
var oscillator3 = context.createOscillator();
oscillator3.type = "square";
oscillator3.frequency.value = 440;
oscillator3.detune.value = 300;
oscillator3.connect(context.destination);
oscillator3.start();
oscillator3.stop(2);
控制發出的聲音
瞭解怎麼利用 Web Audio Api 發聲之後,接著就是要來控制我們所發出來的聲音,但是在我們要控制聲音之前,必須要先了解「節點 AudioNode」這個概念,在 AudioContext 裡,主要就是音樂節點的處理和控制,換個角度思考,AudioContext 就像是一張畫布,在上頭繪製的圖形,就是我們的音符,把音符串在一起,就變成了一首曲子,整個流程圖如下:
簡單來說,source 是我們的音訊來源,不論是用剛剛講的 oscillator 來做,或直接播放音訊,總之音訊來源就都必須轉換為 AudioNode,才能夠做進一步的控制,也就是進入「Processing Nodes」的階段,在這個階段我們就有許多的特殊節點模組 ( Nodes Modules ) 可以進行運算,最後就利用「destination」做輸出的動作,中間的每個步驟,我們都會使用「connect」來做連結,這也是為什麼在使用 Web Audio Api 的程式碼裡頭,都會出現許多 connect,同時也是上面的範例最後用了oscillator3.connect(context.destination);
的原因。
詳細的 Nodes Modules 會在之後的篇幅介紹,這裏我先用到一個最簡單也是最容易理解的 Nodes Modules:「GainNode 音量節點」,顧名思義這就是拿來控制音量使用的,因為我們已經進入 AudioNode 的階段,所以寫法上就要做點小更動,剛剛我們是直接用 oscillator 去連結 context.destination,這裏因為 oscillator 先連結了 gainNode,所以再來我們是要用 gainNode 去連結 context.destination,下面的範例打開以後,就會先聽到兩秒的標準音量,再來又會聽到兩秒變成原本音量 0.3 倍的聲音。( 範例:web-audio-api-demo02.html )
var AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new window.AudioContext;
var oscillator1 = context.createOscillator();
oscillator1.type = "square";
oscillator1.frequency.value = 440;
oscillator1.detune.value = -400;
oscillator1.connect(context.destination);
oscillator1.start(AudioContext.currentTime+0);
oscillator1.stop(AudioContext.currentTime+2);
var oscillator2 = context.createOscillator();
oscillator2.type = "square";
oscillator2.frequency.value = 440;
oscillator2.detune.value = -400;
gainNode = context.createGain(); // 創建 gainNode
gainNode.gain.value = 0.3; // 設定音量
oscillator2.connect(gainNode); // 將聲音連到 gainNode
gainNode.connect(context.destination); // 播放 gainNode
oscillator2.start(AudioContext.currentTime+2);
oscillator2.stop(AudioContext.currentTime+4);
上面的例子用到了一個新的東西叫做currentTime
,currentTime
是從 AudioContext 建立的當下就會產生的時間,時間會不斷的進行直到我們將 AudioContext 移除,因此我們利用currentTime
就可以確保聲音播放的時間,一定會在兩秒後或四秒後停止或播放。
做到這邊,其實我們已經可以做一個很簡單的聲音產生器,並且在聲音播放的過程中還可以去調整聲音,下面這個範例,我做了「播放」與「停止」的按鈕,點選播放就會開始發出聲音,停止就會沒有聲音,一開始以為用 start 和 stop 就可以解決,後來發現一個坑,就是「當 OscillatorNode 開始之後,只會執行一次 start 和 stop」,那該如何讓聲音靜止呢?回想一下剛剛我們用了connect
連結context.destination
來播放,要靜止就只要disconnect
即可,類似把喇叭的音源線拔掉就可以,至於其他的程式,大多都是負責按鈕的點擊事件,以及 range 的拖拉事件,在點擊或拖拉的時候,即時改變 type、frequency、detun 和 volume。
第一部份的程式碼為創建 AudioContext 以及宣告一開始的聲音數值,這裏為什麼要先 disconnect 再 connect 呢。如果沒有這樣做,就會發現每做一個動作,聲音就會不斷的累加上去,就會越來越大聲越來越大聲,因此在這邊每做一個動作就要 disconnect,又為了避免還沒有 connect 的時候就 disconnect 會產生錯誤,因此用了一個 p 來做判斷。
var AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new window.AudioContext;
var oscillator = context.createOscillator();
var sound,
type = 'sine',
frequency = 440,
detune = 0,
volume = 1,
p = 0;
第二部分的程式碼是把畫面上的案扭都根據 id 變成變數:
var startBtn = document.getElementById('startBtn'),
stopBtn = document.getElementById('stopBtn'),
sineBtn = document.getElementById('sineBtn'),
squareBtn = document.getElementById('squareBtn'),
sawtoothBtn = document.getElementById('sawtoothBtn'),
triangleBtn = document.getElementById('triangleBtn'),
frequencyRange = document.getElementById('frequencyRange'),
showFrequency = document.getElementById('showFrequency'),
detuneRange = document.getElementById('detuneRange'),
showDetune = document.getElementById('showDetune'),
volumeRange = document.getElementById('volumeRange'),
showVolume = document.getElementById('showVolume');
接著就是把一些重複的行為做成函式,讓我們可以重複使用,connect 和 disconnect 都寫在這裡面,還有一些按鈕的樣式也寫在這裡。
var _gain = function(t,f,d,g){
oscillator.type = t;
oscillator.frequency.value = f;
oscillator.detune.value = d;
gainNodes = context.createGain();
gainNodes.gain.value = g;
oscillator.connect(gainNodes);
return gainNodes;
}
function _sound(){ if(p == 1){ sound.disconnect(context.destination); sound = _gain(type,frequency,detune,volume); sound.connect(context.destination); } }
function _typeClick(e){
sineBtn.style.outline = 'none';
squareBtn.style.outline = 'none';
sawtoothBtn.style.outline = 'none';
triangleBtn.style.outline = 'none';
e.style.outline = '3px solid #f80';
}
按鈕點選事件的程式,因為按鈕較多,這裏列出三個主要的按鈕:
startBtn.addEventListener('click',function(){
p = 1;
startBtn.disabled = true;
stopBtn.disabled = false;
sound = _gain(type,frequency,detune,volume);
sound.connect(context.destination);
});
stopBtn.addEventListener('click',function(){
p = 0;
startBtn.disabled = false;
stopBtn.disabled = true;
sound.disconnect(context.destination);
});
sineBtn.addEventListener('click',function(){
type = 'sine';
_typeClick(this);
_sound();
});
range 的部分,用了oninput
:
frequencyRange.oninput = function(){
frequency = this.value;
showFrequency.innerHTML = frequency;
_sound();
};
detuneRange.oninput = function(){
detune = this.value;
showDetune.innerHTML = detune;
_sound();
};
volumeRange.oninput = function(){
volume = this.value;
showVolume.innerHTML = volume;
_sound();
};
最後當然就是要讓聲音啟動:
oscillator.start();
小結
到這邊為止,我們已經完成了一個很簡單的聲音產生器,這也是 Web Audio Api 最基本的起手勢,不嫌棄的話就玩玩看吧!( 範例:web-audio-api-demo03.html )