第一個合成器
至此,我們已經擁有撰寫最簡單的合成器的能力,下面的程式碼中僅使用了一個新元件AKPropertySlider
,此元件會創建一個滑條,供使用者操控,並在滑條的值被修改時,將之指定到其他元件上,此處我們要設定的是各個音高的振盪器的音量。
這個專案在啟動時會比較慢,這裡是因為顧及方便性,以及在課程的前半段出現邏輯比較複雜的程式碼會影響學習等原因,在一開始就啟動了所有16
個振盪器,只是其音量皆為0
而已,比較正確的做法是只啟動需要用到的振盪器,並且隨時關掉不再需要的振盪器,有興趣的同學可以自己試著改進(振盪器停止的指令為stop()
)。
在這個專案中,你會發現僅僅是這麼基礎的合成器,已經具有模擬一些真實生活中會出現的聲音的能力:例如僅啟動相距兩格的鍵盤,將會發出近似於撥電話時的等待聲(實際上此聲音由一個約350
赫茲和一個約440
赫茲的正弦波構成)。
這段程式碼中使用了較多未介紹的語法,因此不必太費心於看懂程式碼,自己把玩一下這個合成器,並且觀察AKOutputWaveformPlot
隨著不同的振盪器組合變化的情形,享受成為聲音工程師的樂趣!
import AudioKit import PlaygroundSupport var oscillators:Array<AKOscillator> = [] let mixer = AKMixer() let baseFreq:Array<Float> = [16.35, 18.35, 20.6, 21.83, 24.5, 27.5, 30.87, 32.7] // C, D, E, F, G, A, B, C1 這八個音的頻率 class PlaygroundView: AKPlaygroundView { override func setup() { addTitle("First Pad") // 1. 設置最底層的視圖,此視圖是一個容器,裝載所有其他視圖 let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 600)) // 2. 創建一個視圖來裝載鍵盤,背景設為黑色,並在鍵盤彼此之間留下空隙來視覺化表格 let padContainerView = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400)) padContainerView.layer.backgroundColor = UIColor.black.cgColor // 3. 以for-in迴圈創建振盪器、以及控制振盪器音量的滑條 for i in 1...4 { for j in 1...4 { let mOsc = AKOscillator() let noteOffset:Int = (j-1) + (i-1)*4 mOsc.frequency = baseFreq[noteOffset%8] * (noteOffset/8+1) * 16 let mSlider = AKPropertySlider(property: "", format: "", value: 1, minimum: 0, maximum: 1, color: UIColor(colorLiteralRed: (Float)(0.22*i), green: (Float)(0.22*j), blue: (Float)(0.11*(i+j)), alpha: 1), frame: CGRect(x: 100*(i-1)+1, y: 100*(j-1)+1, width: 98, height: 98), callback: { amp in mOsc.amplitude = amp }) oscillators.append(mOsc) mixer.connect(mOsc) mOsc.start() padContainerView.addSubview(mSlider) } } // 4. 創建輸出波形圖 let plot = AKOutputWaveformPlot(frame: CGRect(x: 0, y: 0, width: 400, height: 200)) plot.plotType = .buffer plot.backgroundColor = AKColor.white plot.originalColor = AKColor.black plot.shouldCenterYAxis = true // 5. 創建一個視圖來裝載波形圖,並將其Y方向壓縮10倍,這屬於偷懶的做法 let plotContainerView = UIView(frame: CGRect(x: 0, y: 400, width: 400, height: 200)) plotContainerView.addSubview(plot) plotContainerView.layer.transform = CATransform3DMakeScale(1, 0.1, 1) containerView.addSubview(padContainerView) containerView.addSubview(plotContainerView) addSubview(containerView) } } AudioKit.output = mixer AudioKit.start() PlaygroundPage.current.needsIndefiniteExecution = true PlaygroundPage.current.liveView = PlaygroundView()