製作更複雜的音效
在前面的幾個單元中,我們較側重於觀察聲音的「頻率」,但並未去實時的改變每個單一振盪器的振幅,它們一但開始震動後,我們就不會去變動它們的振幅,因此它們發出的音量總是恆定的。
還記得我們試圖用rampTime
週期性的改變振盪器的頻率時,它的波型圖嗎?
我們也可以用rampTime
來改變振盪器的振幅,只是在威力強大的AKOutputWaveformPlot
上,並不能明顯地看出效果,因為這個元件主要用途是觀測波形,其運行速度太快,導致我們隨時間改變它的振幅時,看起來卻像把整段波同時壓扁或拉長了。在這個單元中,我們將改用AKNodeOutputPlot
元件來觀察一個振盪器的變化:
let plot = AKNodeOutputPlot(oscillator, frame: CGRect(x: 0, y: 50, width: 440, height: 200)) // 此處的oscillator為欲追蹤的AKNode物件 plot.plotType = .rolling addSubview(plot)
相較於AKOutputWaveformPlot
來說,AKNodeOutputPlot
專注於顯示振幅上的變化,當然頻率的變化還是稍微有影響的,大家可以自行嘗試,此處不贅述這個元件的功能。
首先仿照之前我們用rampTime
來控制頻率的方式,改為控制振幅,使其變換於0.0
至1.0
之間,會輸出這樣非常淺顯易懂的圖形:
在常見的樂器中,音量與時間的關係顯然不可能是這麼恆定的,由於空氣摩擦力會讓震動的能量損失,因此在正常環境中,無論我們敲打或彈奏什麼樂器,之後不去觸碰或影響它,它的震動也很快會停止,振幅的圖形會比較像這樣:
以鋼琴為例,在敲擊琴鍵的瞬間,琴槌擊中琴弦,造成震動而發出聲音,發出的聲波很快到達最大振幅,接著迅速降低到一個水平,此時我們的手指仍然在琴鍵上,隨後我們的手指離開琴鍵,此時琴弦以較緩慢的速度持續損失振幅,最後歸於平靜,這個模式稱為A-D-S-R
模式,廣泛存在傳統的樂器中。
Attack
:從震動開始到發出最大音量的過程。Decay
:從最大音量下降到穩定音量的過程。Sustain
:維持在穩定音量,或極緩慢的損失振幅,直到停止了彈奏樂器的動作為止。Release
:樂器由自然振動,緩慢地歸於平靜的過程。
每個樂器的A-D-S-R
模型各自佔有不同的比例,例如小提琴可能因為樂師持續的拉動琴弓,喇叭等管樂器可能因吹奏者吐出空氣的節奏拉得很長,而擁有較長的Attack
階段,有踏板(Petal
)的鋼琴可能有很長的Sustain
過程等等。而單純用電信原理,以振盪器來發出的聲音很難有這種特色,因此我們必須自己控制參數來模擬A-D-S-R
模型,這也是許多人可能會常在合成器上看到的一個控制模組:「包絡」(Envelope
)。
包絡的Wikipedia
解釋是:一個振盪器的上下兩端的極限端點,串連成二維函數定義出來的線段,如圖所示:
在AudioKit
中,這個所謂的二維函數則是由AKAmplitudeEnvelope
定義,一個最簡單的Envelope
可以由定義每個階段的時間長度來模擬,例如在前文藍色線段的波形圖,其實就是以下的程式碼模擬出來的,這是一個Attack
極短的範例,即使仍然只由一個振盪器發聲,但製造出的音效已經相對更接近於彈奏鋼琴。
// 此處的oscillator為欲套用Envelope的AKNode物件 let envelope = AKAmplitudeEnvelope(oscillator) // 設定各個階段的時長,單位為秒 envelope.attackDuration = 0.01 envelope.decayDuration = 0.09 envelope.sustainLevel = 0.15 envelope.releaseDuration = 0.3 // 在以下的程式碼,每0.7秒切換一次envelope的開關,否則運行一次後音量就永遠是0囉! AKPlaygroundLoop(every: 0.7) { if (envelope.isStarted) { envelope.stop() } else { envelope.start() } } AudioKit.output = envelope AudioKit.start()