AudioKitでシンセサイザーの基本波を鳴らせるアプリを作成

2017年3月24日(更新: 2017年3月24日)

以前、白鍵のみのシンプルなキーボードアプリをAudioKitで作成しました。

今回はこのアプリを改良して、以下の機能を加えてみます。

  • 黒鍵
  • 音の種類(サイン波、矩形波、三角波、ノコギリ波)を変更
  • 音量(波の振幅)の変更

アプリの完成例

以下のように、鍵盤に黒鍵を加え、さらに音の波形の種類を選択するためのピッカーと音の振幅を変更するためのスライダーが追加します。

音量と音色を変更できる鍵盤アプリの完成例

これらのUIパーツの使い方は、以下の記事を御覧ください。

リストから項目を選択するUIPickerViewの使い方

値をつまみのスライドで調整するUISliderの使い方

アプリのソースコード

アプリの全ソースコードです。

必要な外部フレームワークは AudioKit のみです。

ViewController.swift

import UIKit
import AudioKit

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {

    let statusBarHeight = UIApplication.shared.statusBarFrame.height
    
    var oscillator: AKOscillator!
    var waveformPlot: AKNodeOutputPlot!
    
    var amplitudeLabel: UILabel!
    var currentAmplitude: Double = 1.0
    
    // 音の周波数
    let whiteFrequencies:[Double] = [
        523.23,// C
        
        587.34,// D
        
        659.25,// E
        698.45,// F
        
        783.98,// G
        
        879.99,// A
        
        987.75,// B
        1046.50// C
    ]
    
    let blackFrequencies:[Double] = [
        554.36,// C#
        622.25,// D#
        
        739.98,// F#
        830.60,// G#
        932.32,// A#
    ]
    
    // 波形の種類
    let waveType = [
        "sine": AKTable(.sine),
        "sawtooth": AKTable(.sawtooth),
        "square": AKTable(.square),
        "triangle": AKTable(.triangle)
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 波形の種類を設定
        oscillator = AKOscillator(waveform: waveType["sine"]!)
        
        AudioKit.output = oscillator
        AudioKit.start()

        self.createWaveViewer(oscillator)
        self.createSoundTypeSelector()
        self.createVolumeSlider()
        self.createKeyboard()
    }
    
    func createWaveViewer(_ oscillator: AKOscillator) {
        // 鳴っている音の波形ビューを作成
        
        if waveformPlot != nil {
            waveformPlot.removeFromSuperview()
        }
        
        waveformPlot = AKNodeOutputPlot(oscillator, frame: CGRect(
            x: 0,
            y: statusBarHeight,
            width: self.view.frame.width,
            height: 100)
        )
        
        waveformPlot.plotType = .buffer
        self.view.addSubview(waveformPlot)
    }
    
    func createSoundTypeSelector() {
        // 音色を選択するピッカー
        
        let picker = UIPickerView(frame: CGRect(x: 0, y: statusBarHeight + 100, width: self.view.frame.width, height: 100))
        
        picker.delegate = self
        picker.dataSource = self
        
        picker.selectRow(0, inComponent: 0, animated: true)
        self.view.addSubview(picker)
    }
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return waveType.count
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return [String](waveType.keys)[row]
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        let sound = [AKTable](waveType.values)[row];
        self.updateWaveform(sound)
    }
    
    // 音色の更新
    func updateWaveform(_ sound: AKTable) {
        
        AudioKit.stop()
        
        oscillator = AKOscillator(waveform: sound)
        
        self.createWaveViewer(oscillator)
        
        AudioKit.output = oscillator
        AudioKit.start()
    }
    
    
    func createVolumeSlider() {
        // 音量(振幅)を変更するスライダーを作成
        
        let slider = UISlider()
        slider.frame.size.width = 200
        slider.center = self.view.center
        slider.sizeToFit()
        
        slider.minimumValue = 0
        slider.maximumValue = 100
        
        slider.value = 100
        
        slider.addTarget(self, action: #selector(self.changeAmplitude), for: .valueChanged)
        
        self.view.addSubview(slider)
        
        amplitudeLabel = UILabel()
        amplitudeLabel.text = "\(slider.value)%"
        amplitudeLabel.font = UIFont(name: "Arial", size: 24)
        amplitudeLabel.sizeToFit()
        amplitudeLabel.center.x = self.view.center.x
        amplitudeLabel.center.y = self.view.center.y - 24
        self.view.addSubview(amplitudeLabel)
    }
    
    func changeAmplitude(_ sender: UISlider) {
        
        let value = sender.value
        
        currentAmplitude = value / 100
        amplitudeLabel.text = "\(value.rounded())%"
    }
    
    func createKeyboard() {
        // 鍵盤の作成
        
        let maxWhiteKeyLength = whiteFrequencies.count
        let maxBlackKeyLength = blackFrequencies.count
        
        let keyWidth: CGFloat = self.view.frame.width / CGFloat(maxWhiteKeyLength)
        let keyHeight: CGFloat = 160
        
        let blackKeyWidth = keyWidth * 0.8
        let blackKeyHeight = keyHeight * 0.5
        
        // 白鍵の作成
        for i in 0 ..< maxWhiteKeyLength {
            
            let button = UIButton(frame: CGRect(
                x: CGFloat(i) * keyWidth,
                y: self.view.frame.height - keyHeight,
                width: keyWidth,
                height: keyHeight)
            )
            button.backgroundColor = UIColor(white: 1, alpha: 1)
            button.layer.borderWidth = 1
            button.layer.borderColor = UIColor.black.cgColor
            button.layer.cornerRadius = 4
            button.setTitleColor(UIColor.blue, for: .normal)
            button.addTarget(self, action: #selector(self.toggleSound), for: .touchDown)
            button.addTarget(self, action: #selector(self.toggleSound), for: .touchUpInside)
            button.layer.setValue(whiteFrequencies[i], forKey: "freq")
            self.view.addSubview(button)
        }
        
        // 黒鍵の作成
        for i in 0 ..< maxBlackKeyLength {
            
            let button = UIButton(frame: CGRect(
                x: keyWidth / 2 + CGFloat(i) * keyWidth + keyWidth * 0.1,
                y: self.view.frame.height - keyHeight,
                width: blackKeyWidth,
                height: blackKeyHeight)
            )
            
            if i >= 2 {
                button.center.x += keyWidth
            }
            
            button.backgroundColor = UIColor(white: 0, alpha: 1)
            button.layer.borderWidth = 1
            button.layer.borderColor = UIColor.black.cgColor
            button.layer.cornerRadius = 4
            button.setTitleColor(UIColor.blue, for: .normal)
            button.addTarget(self, action: #selector(self.toggleSound), for: .touchDown)
            button.addTarget(self, action: #selector(self.toggleSound), for: .touchUpInside)
            button.layer.setValue(blackFrequencies[i], forKey: "freq")
            self.view.addSubview(button)
        }
    }
    
    func toggleSound(_ sender: UIButton) {
        
        if oscillator.isPlaying {
            oscillator.stop()
        } else {
            oscillator.amplitude = currentAmplitude
            oscillator.frequency = sender.layer.value(forKey: "freq") as! Double
            oscillator.start()
        }
    }
}

まだ、鍵盤の同時押しに対応できていなかったり、細かい動作上の不具合がありますが、キーボードアプリの基本形が出来てきました。

コメントを残す

メールアドレスが公開されることはありません。